Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/src/display_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub use graphic::Graphic;
pub use interactive::{Avm2MousePick, InteractiveObject, TInteractiveObject};
pub use loader_display::LoaderDisplay;
pub use morph_shape::MorphShape;
pub use movie_clip::{MovieClip, MovieClipWeak, Scene};
pub use movie_clip::{MovieClip, MovieClipHandle, MovieClipWeak, Scene};
use ruffle_render::backend::{BitmapCacheEntry, RenderBackend};
use ruffle_render::bitmap::{BitmapHandle, BitmapInfo, PixelSnapping};
use ruffle_render::blend::ExtendedBlendMode;
Expand Down
202 changes: 100 additions & 102 deletions core/src/display_object/movie_clip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
use core::fmt;
use gc_arena::barrier::unlock;
use gc_arena::lock::{Lock, RefLock};
use gc_arena::{Collect, Gc, GcWeak, Mutation};
use gc_arena::{Collect, DynamicRoot, Gc, GcWeak, Mutation, Rootable};
use ruffle_macros::istr;
use ruffle_render::perspective_projection::PerspectiveProjection;
use smallvec::SmallVec;
Expand Down Expand Up @@ -147,6 +147,19 @@
}
}

#[derive(Clone)]
pub struct MovieClipHandle(DynamicRoot<Rootable![MovieClipData<'_>]>);

impl MovieClipHandle {
pub fn stash<'gc>(uc: &UpdateContext<'gc>, this: MovieClip<'gc>) -> Self {
Self(uc.dynamic_root.stash(uc.gc(), this.0))
}

pub fn fetch<'gc>(&self, uc: &UpdateContext<'gc>) -> MovieClip<'gc> {
MovieClip(uc.dynamic_root.fetch(&self.0))
}
}

#[derive(Clone, Collect, HasPrefixField)]
#[collect(no_drop)]
#[repr(C, align(8))]
Expand All @@ -157,9 +170,6 @@

tag_stream_pos: Cell<u64>,

// If this movie was loaded from ImportAssets(2), this will be the parent movie.
importer_movie: Option<Arc<SwfMovie>>,

// Unlike most other DisplayObjects, a MovieClip can have an AVM1
// side and an AVM2 side simultaneously.
object1: Lock<Option<Avm1Object<'gc>>>,
Expand Down Expand Up @@ -250,7 +260,6 @@
hit_area: Lock::new(None),
attached_audio: Lock::new(None),
next_avm1_clip: Lock::new(None),
importer_movie: None,
}
}

Expand Down Expand Up @@ -290,7 +299,7 @@
swf: SwfSlice,
num_frames: u16,
) -> Self {
let shared = MovieClipShared::with_data(id, swf, num_frames, None);
let shared = MovieClipShared::with_data(id, swf, num_frames, None, None);
let data = MovieClipData::new(shared, mc);
data.flags.set(MovieClipFlags::PLAYING);
MovieClip(Gc::new(mc, data))
Expand All @@ -299,15 +308,15 @@
pub fn new_import_assets(
context: &mut UpdateContext<'gc>,
movie: Arc<SwfMovie>,
parent: Arc<SwfMovie>,
parent: MovieClip<'gc>,
) -> Self {
let num_frames = movie.num_frames();
let loader_info = None;
let shared = MovieClipShared::with_data(0, movie.into(), num_frames, loader_info);
let shared =
MovieClipShared::with_data(0, movie.into(), num_frames, loader_info, Some(parent));

let mut data = MovieClipData::new(shared, context.gc());
let data = MovieClipData::new(shared, context.gc());
data.flags.set(MovieClipFlags::PLAYING);
data.importer_movie = Some(parent);
MovieClip(Gc::new(context.gc(), data))
}

Expand All @@ -330,8 +339,13 @@
None
};

let shared =
MovieClipShared::with_data(0, movie.clone().into(), movie.num_frames(), loader_info);
let shared = MovieClipShared::with_data(
0,
movie.clone().into(),
movie.num_frames(),
loader_info,
None,
);
let data = MovieClipData::new(shared, activation.gc());
data.flags.set(MovieClipFlags::PLAYING);
data.base.base.set_is_root(true);
Expand Down Expand Up @@ -374,7 +388,7 @@

unlock!(write, MovieClipData, shared).set(Gc::new(
context.gc(),
MovieClipShared::with_data(0, movie.into(), total_frames, loader_info),
MovieClipShared::with_data(0, movie.into(), total_frames, loader_info, None),
));
write.tag_stream_pos.set(0);
write.flags.set(MovieClipFlags::PLAYING);
Expand Down Expand Up @@ -508,9 +522,7 @@
TagCode::DefineText2 => shared.define_text(context, reader, 2),
TagCode::DoInitAction => self.do_init_action(context, reader, tag_len),
TagCode::DefineSceneAndFrameLabelData => shared.scene_and_frame_labels(reader),
TagCode::ExportAssets => {
shared.export_assets(context, reader, self.0.importer_movie.as_ref())
}
TagCode::ExportAssets => shared.export_assets(context, reader),
TagCode::FrameLabel => shared.frame_label(reader),
TagCode::JpegTables => shared.jpeg_tables(context, reader),
TagCode::ShowFrame => shared.show_frame(reader, tag_len),
Expand All @@ -519,8 +531,8 @@
TagCode::SoundStreamHead2 => shared.sound_stream_head(reader, 2),
TagCode::VideoFrame => shared.preload_video_frame(context, reader),
TagCode::DefineBinaryData => shared.define_binary_data(context, reader),
TagCode::ImportAssets => shared.import_assets(context, reader, chunk_limit),
TagCode::ImportAssets2 => shared.import_assets_2(context, reader, chunk_limit),
TagCode::ImportAssets => self.import_assets(context, reader, chunk_limit, 1),

Check warning on line 534 in core/src/display_object/movie_clip.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (534)
TagCode::ImportAssets2 => self.import_assets(context, reader, chunk_limit, 2),
TagCode::DoAbc | TagCode::DoAbc2 => shared.preload_bytecode_tag(tag_code, reader),
TagCode::SymbolClass => shared.preload_symbol_class(reader),
TagCode::End => {
Expand All @@ -545,9 +557,7 @@
};
let is_finished = end_tag_found || result.is_err() || !result.unwrap_or_default();

if let Some(importer_movie) = self.0.importer_movie.clone() {
shared.import_exports_of_importer(context, importer_movie);
}
shared.import_exports_of_importer(context);

if is_finished {
if progress.cur_preload_frame.get() == 1 {
Expand All @@ -573,15 +583,29 @@
reader: &mut SwfStream<'_>,
tag_len: usize,
) -> Result<(), Error> {
if self.movie().is_action_script_3() {
tracing::warn!("DoInitAction tag in AVM2 movie");
return Ok(());
let mut target = self;
loop {
let shared = target.0.shared.get();
if shared.movie().is_action_script_3() {
tracing::warn!("DoInitAction tag in AVM2 movie");
return Ok(());

Check warning on line 591 in core/src/display_object/movie_clip.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (590–591)
}

// `DoInitAction`s always execute in the context of their importer movie.
let Some(parent) = shared.importer_movie else {
break;
};

target = parent;
}

let start = reader.as_slice();
// Queue the init actions.

// TODO: Init actions are supposed to be executed once, and it gives a
// sprite ID... how does that work?
// TODO: what happens with `DoInitAction` blocks nested in a `DefineSprite`?
// The SWF spec forbids this, but Ruffle will currently execute them in the context
// of the character itself, which is probably nonsense.
let _sprite_id = reader.read_u16()?;
let num_read = reader.pos(start);

Expand All @@ -593,7 +617,7 @@
.resize_to_reader(reader, tag_len - num_read);

if !slice.is_empty() {
Avm1::run_stack_frame_for_init_action(self.into(), slice, context);
Avm1::run_stack_frame_for_init_action(target.into(), slice, context);
}

Ok(())
Expand Down Expand Up @@ -670,6 +694,43 @@
Ok(None)
}

#[inline]
fn import_assets(
self,
context: &mut UpdateContext<'gc>,
reader: &mut SwfStream<'_>,
_chunk_limit: &mut ExecutionLimit,
version: u8,
) -> Result<(), Error> {
let (url, exported_assets) = match version {
1 => reader.read_import_assets()?,

Check warning on line 706 in core/src/display_object/movie_clip.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (706)
2 => reader.read_import_assets_2()?,
_ => unreachable!(),

Check warning on line 708 in core/src/display_object/movie_clip.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (708)
};

let mc = context.gc();
let library = context.library.library_for_movie_mut(self.movie());

let asset_url = url.to_string_lossy(UTF_8);

let request = Request::get(asset_url);

for asset in exported_assets {
let name = asset.name.decode(reader.encoding());
let name = AvmString::new(mc, name);
let id = asset.id;
tracing::debug!("Importing asset: {} (ID: {})", name, id);

library.register_import(name, id);
}

let fut = LoadManager::load_asset_movie(context, request, self);

context.navigator.spawn_future(fut);

Ok(())
}

pub fn playing(self) -> bool {
self.0.playing()
}
Expand Down Expand Up @@ -3733,12 +3794,11 @@
}

#[inline]
fn import_exports_of_importer(
&self,
context: &mut UpdateContext<'gc>,
importer: Arc<SwfMovie>,
) {
let Some(importer_library) = context.library.library_for_movie(importer) else {
fn import_exports_of_importer(&self, context: &mut UpdateContext<'gc>) {
let Some(importer_library) = self
.importer_movie
.and_then(|mc| context.library.library_for_movie(mc.movie()))
else {
return;
};

Expand All @@ -3760,73 +3820,6 @@
}
}

#[inline]
fn import_assets(
&self,
context: &mut UpdateContext<'gc>,
reader: &mut SwfStream<'a>,
chunk_limit: &mut ExecutionLimit,
) -> Result<(), Error> {
let import_assets = reader.read_import_assets()?;
self.import_assets_load(
context,
reader,
import_assets.0,
import_assets.1,
chunk_limit,
)
}

#[inline]
fn import_assets_2(
&self,
context: &mut UpdateContext<'gc>,
reader: &mut SwfStream<'a>,
chunk_limit: &mut ExecutionLimit,
) -> Result<(), Error> {
let import_assets = reader.read_import_assets_2()?;
self.import_assets_load(
context,
reader,
import_assets.0,
import_assets.1,
chunk_limit,
)
}

#[inline]
fn import_assets_load(
&self,
context: &mut UpdateContext<'gc>,
reader: &mut SwfStream<'a>,
url: &swf::SwfStr,
exported_assets: Vec<swf::ExportedAsset>,
_chunk_limit: &mut ExecutionLimit,
) -> Result<(), Error> {
let mc = context.gc();
let library = self.library_mut(context);

let asset_url = url.to_string_lossy(UTF_8);

let request = Request::get(asset_url);

for asset in exported_assets {
let name = asset.name.decode(reader.encoding());
let name = AvmString::new(mc, name);
let id = asset.id;
tracing::debug!("Importing asset: {} (ID: {})", name, id);

library.register_import(name, id);
}

let player = context.player.clone();
let fut = LoadManager::load_asset_movie(player, request, self.movie());

context.navigator.spawn_future(fut);

Ok(())
}

#[inline]
fn script_limits(&self, reader: &mut SwfStream<'a>, avm: &mut Avm1<'gc>) -> Result<(), Error> {
let max_recursion_depth = reader.read_u16()?;
Expand Down Expand Up @@ -3869,9 +3862,9 @@
&self,
context: &mut UpdateContext<'gc>,
reader: &mut SwfStream<'a>,
importer_movie: Option<&Arc<SwfMovie>>,
) -> Result<(), Error> {
let exports = reader.read_export_assets()?;
let importer_movie = self.importer_movie.map(|mc| mc.movie());
for export in exports {
let name = export.name.decode(reader.encoding());
let name = AvmString::new(context.gc(), name);
Expand All @@ -3883,7 +3876,7 @@
Self::register_export(context, export.id, name, self.movie());
tracing::debug!("register_export asset: {} (ID: {})", name, export.id);

if let Some(parent) = importer_movie {
if let Some(parent) = &importer_movie {
let parent_library = context.library.library_for_movie_mut(parent.clone());

if let Some(id) = parent_library.character_id_by_import_name(name) {
Expand Down Expand Up @@ -4486,6 +4479,9 @@
/// However, it will be set for an AVM1 movie loaded from AVM2
/// via `Loader`
loader_info: Option<LoaderInfoObject<'gc>>,

// If this movie was loaded from ImportAssets(2), this will be the root MovieClip of the parent movie.
importer_movie: Option<MovieClip<'gc>>,
}

#[derive(Default)]
Expand Down Expand Up @@ -4514,7 +4510,7 @@

impl<'gc> MovieClipShared<'gc> {
fn empty(movie: Arc<SwfMovie>) -> Self {
let mut s = Self::with_data(0, SwfSlice::empty(movie), 1, None);
let mut s = Self::with_data(0, SwfSlice::empty(movie), 1, None, None);

*s.preload_progress.cur_preload_frame.get_mut() = s.header_frames + 1;

Expand All @@ -4526,6 +4522,7 @@
swf: SwfSlice,
header_frames: FrameNumber,
loader_info: Option<LoaderInfoObject<'gc>>,
importer_movie: Option<MovieClip<'gc>>,
) -> Self {
Self {
cell: Default::default(),
Expand All @@ -4536,6 +4533,7 @@
exported_name: Lock::new(None),
avm2_class: Lock::new(None),
loader_info,
importer_movie,
}
}

Expand Down
Loading
Loading