core: Implement chunked decoding infrastructure.
We currently do not actually enforce a decoding limit; just add the functionality necessary to do so.
This commit is contained in:
parent
c63f915ae2
commit
4e9bb3a173
|
@ -683,7 +683,7 @@ fn load_playerglobal<'gc>(
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = tag_utils::decode_tags(&mut reader, tag_callback, TagCode::End);
|
let _ = tag_utils::decode_tags(&mut reader, tag_callback, TagCode::End, None);
|
||||||
macro_rules! avm2_system_classes_playerglobal {
|
macro_rules! avm2_system_classes_playerglobal {
|
||||||
($activation:expr, $script:expr, [$(($package:expr, $class_name:expr, $field:ident)),* $(,)?]) => {
|
($activation:expr, $script:expr, [$(($package:expr, $class_name:expr, $field:ident)),* $(,)?]) => {
|
||||||
$(
|
$(
|
||||||
|
|
|
@ -329,7 +329,8 @@ impl Iterator for StreamTagReader {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut reader = self.swf_data.read_from(self.pos as u64);
|
let mut reader = self.swf_data.read_from(self.pos as u64);
|
||||||
let _ = crate::tag_utils::decode_tags(&mut reader, tag_callback, TagCode::ShowFrame);
|
let _ =
|
||||||
|
crate::tag_utils::decode_tags(&mut reader, tag_callback, TagCode::ShowFrame, None);
|
||||||
self.pos = reader.get_ref().as_ptr() as usize - swf_data.as_ref().as_ptr() as usize;
|
self.pos = reader.get_ref().as_ptr() as usize - swf_data.as_ref().as_ptr() as usize;
|
||||||
|
|
||||||
// If we hit a SoundStreamBlock within this frame, return it. Otherwise, the stream should end.
|
// If we hit a SoundStreamBlock within this frame, return it. Otherwise, the stream should end.
|
||||||
|
|
|
@ -196,7 +196,7 @@ impl<'gc> MovieClip<'gc> {
|
||||||
base: Default::default(),
|
base: Default::default(),
|
||||||
static_data: Gc::allocate(
|
static_data: Gc::allocate(
|
||||||
gc_context,
|
gc_context,
|
||||||
MovieClipStatic::with_data(id, swf, num_frames, None, gc_context),
|
MovieClipStatic::with_data(id, swf, num_frames, None, None, gc_context),
|
||||||
),
|
),
|
||||||
tag_stream_pos: 0,
|
tag_stream_pos: 0,
|
||||||
current_frame: 0,
|
current_frame: 0,
|
||||||
|
@ -256,6 +256,10 @@ impl<'gc> MovieClip<'gc> {
|
||||||
movie.clone().into(),
|
movie.clone().into(),
|
||||||
num_frames,
|
num_frames,
|
||||||
loader_info,
|
loader_info,
|
||||||
|
Some(GcCell::allocate(
|
||||||
|
activation.context.gc_context,
|
||||||
|
PreloadProgress::default(),
|
||||||
|
)),
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -334,6 +338,10 @@ impl<'gc> MovieClip<'gc> {
|
||||||
movie.into(),
|
movie.into(),
|
||||||
total_frames,
|
total_frames,
|
||||||
loader_info.map(|l| l.into()),
|
loader_info.map(|l| l.into()),
|
||||||
|
Some(GcCell::allocate(
|
||||||
|
context.gc_context,
|
||||||
|
PreloadProgress::default(),
|
||||||
|
)),
|
||||||
context.gc_context,
|
context.gc_context,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -346,15 +354,35 @@ impl<'gc> MovieClip<'gc> {
|
||||||
drop(mc);
|
drop(mc);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn preload(self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
/// Preload a chunk of the movie.
|
||||||
|
///
|
||||||
|
/// A "chunk" is an implementor-chosen number of tags that are parsed
|
||||||
|
/// before this function returns. This function will only parse up to a
|
||||||
|
/// certain number of tags, and then return. If this function returns false,
|
||||||
|
/// then the preload didn't complete and further preloads should occur
|
||||||
|
/// until this returns true.
|
||||||
|
///
|
||||||
|
/// The chunked preload assumes that preloading is happening within the
|
||||||
|
/// context of an event loop. As such, multiple chunks should be processed
|
||||||
|
/// in between yielding to the underlying event loop, either through
|
||||||
|
/// `await`, returning to the loop directly, or some other mechanism.
|
||||||
|
pub fn preload(self, context: &mut UpdateContext<'_, 'gc, '_>) -> bool {
|
||||||
use swf::TagCode;
|
use swf::TagCode;
|
||||||
// TODO: Re-creating static data because preload step occurs after construction.
|
// TODO: Re-creating static data because preload step occurs after construction.
|
||||||
// Should be able to hoist this up somewhere, or use MaybeUninit.
|
// Should be able to hoist this up somewhere, or use MaybeUninit.
|
||||||
let mut static_data = (&*self.0.read().static_data).clone();
|
let mut static_data = (&*self.0.read().static_data).clone();
|
||||||
let data = self.0.read().static_data.swf.clone();
|
let data = self.0.read().static_data.swf.clone();
|
||||||
let mut reader = data.read_from(0);
|
let mut reader = data.read_from(0);
|
||||||
let mut cur_frame = 1;
|
let (mut cur_frame, mut start_pos) = if let Some(progress) = static_data.preload_progress {
|
||||||
let mut start_pos = 0;
|
(
|
||||||
|
progress.read().cur_preload_frame,
|
||||||
|
progress.read().last_frame_start_pos,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
log::warn!("Preloading a non-root movie.");
|
||||||
|
|
||||||
|
(1, 0)
|
||||||
|
};
|
||||||
|
|
||||||
let tag_callback = |reader: &mut SwfStream<'_>, tag_code, tag_len| match tag_code {
|
let tag_callback = |reader: &mut SwfStream<'_>, tag_code, tag_len| match tag_code {
|
||||||
TagCode::CsmTextSettings => self
|
TagCode::CsmTextSettings => self
|
||||||
|
@ -514,7 +542,18 @@ impl<'gc> MovieClip<'gc> {
|
||||||
.define_binary_data(context, reader),
|
.define_binary_data(context, reader),
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
};
|
};
|
||||||
let _ = tag_utils::decode_tags(&mut reader, tag_callback, TagCode::End);
|
let is_finished = tag_utils::decode_tags(&mut reader, tag_callback, TagCode::End, None);
|
||||||
|
|
||||||
|
// These variables will be persisted to be picked back up in the next
|
||||||
|
// chunk.
|
||||||
|
if let Some(progress) = static_data.preload_progress {
|
||||||
|
let mut write = progress.write(context.gc_context);
|
||||||
|
|
||||||
|
write.next_preload_chunk =
|
||||||
|
(reader.get_ref().as_ptr() as u64).saturating_sub(data.data().as_ptr() as u64);
|
||||||
|
write.cur_preload_frame = cur_frame;
|
||||||
|
write.last_frame_start_pos = start_pos;
|
||||||
|
}
|
||||||
|
|
||||||
// End-of-clip should be treated as ShowFrame
|
// End-of-clip should be treated as ShowFrame
|
||||||
self.0
|
self.0
|
||||||
|
@ -524,6 +563,8 @@ impl<'gc> MovieClip<'gc> {
|
||||||
|
|
||||||
self.0.write(context.gc_context).static_data =
|
self.0.write(context.gc_context).static_data =
|
||||||
Gc::allocate(context.gc_context, static_data);
|
Gc::allocate(context.gc_context, static_data);
|
||||||
|
|
||||||
|
is_finished.unwrap_or(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -1082,7 +1123,7 @@ 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, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1165,7 +1206,7 @@ impl<'gc> MovieClip<'gc> {
|
||||||
TagCode::SoundStreamBlock if run_sounds => self.sound_stream_block(context, reader),
|
TagCode::SoundStreamBlock if run_sounds => self.sound_stream_block(context, reader),
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
};
|
};
|
||||||
let _ = tag_utils::decode_tags(&mut reader, tag_callback, TagCode::ShowFrame);
|
let _ = tag_utils::decode_tags(&mut reader, tag_callback, TagCode::ShowFrame, None);
|
||||||
|
|
||||||
// On AS3, we deliberately run all removals before the frame number or
|
// On AS3, we deliberately run all removals before the frame number or
|
||||||
// tag position updates. This ensures that code that runs gotos when a
|
// tag position updates. This ensures that code that runs gotos when a
|
||||||
|
@ -1504,7 +1545,7 @@ impl<'gc> MovieClip<'gc> {
|
||||||
),
|
),
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
};
|
};
|
||||||
let _ = tag_utils::decode_tags(&mut reader, tag_callback, TagCode::ShowFrame);
|
let _ = tag_utils::decode_tags(&mut reader, tag_callback, TagCode::ShowFrame, None);
|
||||||
}
|
}
|
||||||
let hit_target_frame = self.0.read().current_frame == frame;
|
let hit_target_frame = self.0.read().current_frame == frame;
|
||||||
|
|
||||||
|
@ -3635,6 +3676,30 @@ impl Default for Scene {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The load progress for a given SWF.
|
||||||
|
#[derive(Clone, Collect)]
|
||||||
|
#[collect(require_static)]
|
||||||
|
struct PreloadProgress {
|
||||||
|
/// The SWF offset to start the next preload chunk from.
|
||||||
|
next_preload_chunk: u64,
|
||||||
|
|
||||||
|
/// The current frame being preloaded.
|
||||||
|
cur_preload_frame: u16,
|
||||||
|
|
||||||
|
/// The SWF offset that the current frame started in.
|
||||||
|
last_frame_start_pos: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PreloadProgress {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
next_preload_chunk: 0,
|
||||||
|
cur_preload_frame: 1,
|
||||||
|
last_frame_start_pos: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Static data shared between all instances of a movie clip.
|
/// Static data shared between all instances of a movie clip.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Clone, Collect)]
|
#[derive(Clone, Collect)]
|
||||||
|
@ -3662,11 +3727,16 @@ struct MovieClipStatic<'gc> {
|
||||||
/// However, it will be set for an AVM1 movie loaded from AVM2
|
/// However, it will be set for an AVM1 movie loaded from AVM2
|
||||||
/// via `Loader`
|
/// via `Loader`
|
||||||
loader_info: Option<Avm2Object<'gc>>,
|
loader_info: Option<Avm2Object<'gc>>,
|
||||||
|
|
||||||
|
/// Preload progress for a given SWF.
|
||||||
|
///
|
||||||
|
/// Only present for root movies in AVM1 or AVM2.
|
||||||
|
preload_progress: Option<GcCell<'gc, PreloadProgress>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> MovieClipStatic<'gc> {
|
impl<'gc> MovieClipStatic<'gc> {
|
||||||
fn empty(movie: Arc<SwfMovie>, gc_context: MutationContext<'gc, '_>) -> Self {
|
fn empty(movie: Arc<SwfMovie>, gc_context: MutationContext<'gc, '_>) -> Self {
|
||||||
Self::with_data(0, SwfSlice::empty(movie), 1, None, gc_context)
|
Self::with_data(0, SwfSlice::empty(movie), 1, None, None, gc_context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_data(
|
fn with_data(
|
||||||
|
@ -3674,6 +3744,7 @@ impl<'gc> MovieClipStatic<'gc> {
|
||||||
swf: SwfSlice,
|
swf: SwfSlice,
|
||||||
total_frames: FrameNumber,
|
total_frames: FrameNumber,
|
||||||
loader_info: Option<Avm2Object<'gc>>,
|
loader_info: Option<Avm2Object<'gc>>,
|
||||||
|
preload_progress: Option<GcCell<'gc, PreloadProgress>>,
|
||||||
gc_context: MutationContext<'gc, '_>,
|
gc_context: MutationContext<'gc, '_>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -3686,6 +3757,7 @@ impl<'gc> MovieClipStatic<'gc> {
|
||||||
audio_stream_handle: None,
|
audio_stream_handle: None,
|
||||||
exported_name: GcCell::allocate(gc_context, None),
|
exported_name: GcCell::allocate(gc_context, None),
|
||||||
loader_info,
|
loader_info,
|
||||||
|
preload_progress,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1329,7 +1329,11 @@ impl Player {
|
||||||
fn preload(&mut self) {
|
fn preload(&mut self) {
|
||||||
self.mutate_with_update_context(|context| {
|
self.mutate_with_update_context(|context| {
|
||||||
let root = context.stage.root_clip();
|
let root = context.stage.root_clip();
|
||||||
root.as_movie_clip().unwrap().preload(context);
|
let mut preload_done = false;
|
||||||
|
|
||||||
|
while !preload_done {
|
||||||
|
preload_done = root.as_movie_clip().unwrap().preload(context);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if self.swf.is_action_script_3() && self.warn_on_unsupported_content {
|
if self.swf.is_action_script_3() && self.warn_on_unsupported_content {
|
||||||
self.ui.display_unsupported_message();
|
self.ui.display_unsupported_message();
|
||||||
|
|
|
@ -341,15 +341,43 @@ impl SwfSlice {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decode tags from a SWF stream reader.
|
||||||
|
///
|
||||||
|
/// The given `tag_callback` will be called for each decoded tag. It will be
|
||||||
|
/// provided with the stream to read from, the tag code read, and the tag's
|
||||||
|
/// size. The callback is responsible for (optionally) parsing the contents of
|
||||||
|
/// the tag; otherwise, it will be skipped.
|
||||||
|
///
|
||||||
|
/// Decoding will terminate when the following conditions occur:
|
||||||
|
///
|
||||||
|
/// * After the given `stop_tag` is encountered and passed to the callback
|
||||||
|
/// * The decoder encounters a tag longer than the underlying SWF slice
|
||||||
|
/// * The decoder decodes more than `chunk_limit` tags (if provided), in which
|
||||||
|
/// case this function also returns `false`
|
||||||
|
/// * The SWF stream is otherwise corrupt or unreadable (indicated as an error
|
||||||
|
/// result)
|
||||||
|
///
|
||||||
|
/// Decoding will also log tags longer than the SWF slice, error messages
|
||||||
|
/// yielded from the tag callback, and unknown tags. It will *only* return an
|
||||||
|
/// error message if the SWF tag itself could not be parsed. Otherwise, it
|
||||||
|
/// returns `true` if decoding progressed to the stop tag or EOF, or `false` if
|
||||||
|
/// the chunk limit was reached.
|
||||||
pub fn decode_tags<'a, F>(
|
pub fn decode_tags<'a, F>(
|
||||||
reader: &mut SwfStream<'a>,
|
reader: &mut SwfStream<'a>,
|
||||||
mut tag_callback: F,
|
mut tag_callback: F,
|
||||||
stop_tag: TagCode,
|
stop_tag: TagCode,
|
||||||
) -> Result<(), Error>
|
mut chunk_limit: Option<usize>,
|
||||||
|
) -> Result<bool, Error>
|
||||||
where
|
where
|
||||||
F: for<'b> FnMut(&'b mut SwfStream<'a>, TagCode, usize) -> DecodeResult,
|
F: for<'b> FnMut(&'b mut SwfStream<'a>, TagCode, usize) -> DecodeResult,
|
||||||
{
|
{
|
||||||
loop {
|
loop {
|
||||||
|
if let Some(chunk_limit) = chunk_limit {
|
||||||
|
if chunk_limit < 1 {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let (tag_code, tag_len) = reader.read_tag_code_and_length()?;
|
let (tag_code, tag_len) = reader.read_tag_code_and_length()?;
|
||||||
if tag_len > reader.get_ref().len() {
|
if tag_len > reader.get_ref().len() {
|
||||||
log::error!("Unexpected EOF when reading tag");
|
log::error!("Unexpected EOF when reading tag");
|
||||||
|
@ -376,7 +404,11 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
*reader.get_mut() = end_slice;
|
*reader.get_mut() = end_slice;
|
||||||
|
|
||||||
|
if let Some(ref mut chunk_limit) = chunk_limit {
|
||||||
|
*chunk_limit -= 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue