core: Store clip frame labels

Bump swf-rs revision to allow for read_frame_label calls.
Also fix read_place_object to take an explicit length.
This commit is contained in:
Mike Welsh 2019-08-15 17:00:48 -07:00
parent 7e1b1e0357
commit fe086c11ff
3 changed files with 129 additions and 72 deletions

View File

@ -570,14 +570,19 @@ impl Avm1 {
fn action_goto_label(
&mut self,
_context: &mut ActionContext,
_label: &str,
context: &mut ActionContext,
label: &str,
) -> Result<(), Error> {
// let mut display_object = context.active_clip.borrow_mut();
// let mut clip = display_object.as_movie_clip_mut().unwrap();
//if let Some(frame) = clip.frame_label_to_number(label, context) {
//clip.goto_frame(frame !set_playing)
//}
let mut display_object = context.active_clip.write(context.gc_context);
if let Some(clip) = display_object.as_movie_clip_mut() {
if let Some(frame) = clip.frame_label_to_number(label) {
clip.goto_frame(frame, true);
} else {
log::warn!("ActionGoToLabel: Frame label '{}' not found", label);
}
} else {
log::warn!("ActionGoToLabel: Expected movie clip");
}
Ok(())
}

View File

@ -11,6 +11,7 @@ use crate::player::{RenderContext, UpdateContext};
use crate::prelude::*;
use crate::tag_utils::{self, DecodeResult, SwfStream};
use crate::text::Text;
use gc_arena::{Gc, MutationContext};
use std::collections::{BTreeMap, HashMap};
use swf::read::SwfRead;
@ -20,42 +21,33 @@ type FrameNumber = u16;
#[derive(Clone)]
pub struct MovieClip<'gc> {
base: DisplayObjectBase<'gc>,
id: CharacterId,
tag_stream_start: u64,
static_data: Gc<'gc, MovieClipStatic>,
tag_stream_pos: u64,
tag_stream_len: usize,
is_playing: bool,
goto_queue: Vec<FrameNumber>,
current_frame: FrameNumber,
total_frames: FrameNumber,
audio_stream_info: Option<swf::SoundStreamHead>,
audio_stream: Option<AudioStreamHandle>,
children: BTreeMap<Depth, DisplayNode<'gc>>,
variables: HashMap<String, avm1::Value>,
}
impl<'gc> MovieClip<'gc> {
pub fn new() -> Self {
pub fn new(gc_context: MutationContext<'gc, '_>) -> Self {
Self {
base: Default::default(),
id: 0,
tag_stream_start: 0,
static_data: Gc::allocate(gc_context, MovieClipStatic::default()),
tag_stream_pos: 0,
tag_stream_len: 0,
is_playing: true,
goto_queue: Vec::new(),
current_frame: 0,
total_frames: 1,
audio_stream: None,
audio_stream_info: None,
children: BTreeMap::new(),
variables: HashMap::new(),
}
}
pub fn new_with_data(
gc_context: MutationContext<'gc, '_>,
id: CharacterId,
tag_stream_start: u64,
tag_stream_len: usize,
@ -63,16 +55,19 @@ impl<'gc> MovieClip<'gc> {
) -> Self {
Self {
base: Default::default(),
static_data: Gc::allocate(gc_context, MovieClipStatic {
id,
tag_stream_start,
tag_stream_pos: 0,
tag_stream_len,
total_frames: num_frames,
audio_stream_info: None,
frame_labels: HashMap::new(),
}),
tag_stream_pos: 0,
is_playing: true,
goto_queue: Vec::new(),
current_frame: 0,
audio_stream: None,
audio_stream_info: None,
total_frames: num_frames,
children: BTreeMap::new(),
variables: HashMap::new(),
}
@ -83,7 +78,7 @@ impl<'gc> MovieClip<'gc> {
}
pub fn next_frame(&mut self) {
if self.current_frame < self.total_frames {
if self.current_frame() < self.total_frames() {
self.goto_frame(self.current_frame + 1, true);
}
}
@ -151,12 +146,12 @@ impl<'gc> MovieClip<'gc> {
}
pub fn total_frames(&self) -> FrameNumber {
self.total_frames
self.static_data.total_frames
}
pub fn frames_loaded(&self) -> FrameNumber {
// TODO(Herschel): root needs to progressively stream in frames.
self.total_frames
self.static_data.total_frames
}
pub fn get_child_by_name(&self, name: &str) -> Option<&DisplayNode<'gc>> {
@ -168,25 +163,9 @@ impl<'gc> MovieClip<'gc> {
pub fn frame_label_to_number(
&self,
_frame_label: &str,
_context: &mut UpdateContext<'_, '_, '_>,
frame_label: &str,
) -> Option<FrameNumber> {
// TODO(Herschel): We should cache the labels in the preload step.
// let mut reader = self.reader(context);
// use swf::Tag;
// let mut frame_num = 1;
// while let Ok(Some(tag)) = reader.read_tag() {
// match tag {
// Tag::FrameLabel { label, .. } => {
// if label == frame_label {
// return Some(frame_num);
// }
// }
// Tag::ShowFrame => frame_num += 1,
// _ => (),
// }
// }
None
self.static_data.frame_labels.get(frame_label).copied()
}
pub fn run_goto_queue(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) {
@ -228,13 +207,25 @@ impl<'gc> MovieClip<'gc> {
self.variables.insert(var_name.to_owned(), value);
}
fn id(&self) -> CharacterId {
self.static_data.id
}
fn tag_stream_start(&self) -> u64 {
self.static_data.tag_stream_start
}
fn tag_stream_len(&self) -> usize {
self.static_data.tag_stream_len
}
fn reader<'a>(
&self,
context: &UpdateContext<'a, '_, '_>,
) -> swf::read::Reader<std::io::Cursor<&'a [u8]>> {
let mut cursor = std::io::Cursor::new(
&context.swf_data[self.tag_stream_start as usize
..self.tag_stream_start as usize + self.tag_stream_len],
&context.swf_data[self.tag_stream_start() as usize
..self.tag_stream_start() as usize + self.tag_stream_len()],
);
cursor.set_position(self.tag_stream_pos);
swf::read::Reader::new(cursor, context.swf_version)
@ -245,7 +236,7 @@ impl<'gc> MovieClip<'gc> {
only_display_actions: bool,
) {
// Advance frame number.
if self.current_frame < self.total_frames {
if self.current_frame() < self.total_frames() {
self.current_frame += 1;
} else {
self.current_frame = 1;
@ -293,9 +284,12 @@ impl<'gc> DisplayObject<'gc> for MovieClip<'gc> {
impl_display_object!(base);
fn preload(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) {
// Ignore error for now.
// TODO: Re-creating static data because preload step occurs after construction.
// Should be able to hoist this up somewhere, or use MaybeUnit.
let mut static_data = (&*self.static_data).clone();
use swf::TagCode;
let mut reader = self.reader(context);
let mut cur_frame = 1;
let mut ids = fnv::FnvHashMap::default();
let tag_callback = |reader: &mut _, tag_code, tag_len| match tag_code {
TagCode::DefineBits => self.define_bits(context, reader, tag_len),
@ -319,6 +313,7 @@ impl<'gc> DisplayObject<'gc> for MovieClip<'gc> {
TagCode::DefineSound => self.define_sound(context, reader, tag_len),
TagCode::DefineSprite => self.define_sprite(context, reader, tag_len),
TagCode::DefineText => self.define_text(context, reader),
TagCode::FrameLabel => self.frame_label(context, reader, tag_len, cur_frame, &mut static_data),
TagCode::JpegTables => self.jpeg_tables(context, reader, tag_len),
TagCode::PlaceObject => self.preload_place_object(context, reader, tag_len, &mut ids, 1),
TagCode::PlaceObject2 => self.preload_place_object(context, reader, tag_len, &mut ids, 2),
@ -326,14 +321,16 @@ impl<'gc> DisplayObject<'gc> for MovieClip<'gc> {
TagCode::PlaceObject4 => self.preload_place_object(context, reader, tag_len, &mut ids, 4),
TagCode::RemoveObject => self.preload_remove_object(context, reader, &mut ids, 1),
TagCode::RemoveObject2 => self.preload_remove_object(context, reader, &mut ids, 2),
TagCode::SoundStreamHead => self.preload_sound_stream_head(context, reader, 1),
TagCode::SoundStreamHead2 => self.preload_sound_stream_head(context, reader, 2),
TagCode::ShowFrame => self.preload_show_frame(context, reader, &mut cur_frame),
TagCode::SoundStreamHead => self.preload_sound_stream_head(context, reader, &mut static_data, 1),
TagCode::SoundStreamHead2 => self.preload_sound_stream_head(context, reader, &mut static_data, 2),
TagCode::SoundStreamBlock => self.preload_sound_stream_block(context, reader, tag_len),
_ => Ok(()),
};
let _ = tag_utils::decode_tags(&mut reader, tag_callback, TagCode::End);
if self.audio_stream_info.is_some() {
context.audio.preload_sound_stream_end(self.id);
self.static_data = Gc::allocate(context.gc_context, static_data);
if self.static_data.audio_stream_info.is_some() {
context.audio.preload_sound_stream_end(self.id());
}
}
@ -395,12 +392,6 @@ impl<'gc> DisplayObject<'gc> for MovieClip<'gc> {
}
}
impl Default for MovieClip<'_> {
fn default() -> Self {
MovieClip::new()
}
}
unsafe impl<'gc> gc_arena::Collect for MovieClip<'gc> {
#[inline]
fn trace(&self, cc: gc_arena::CollectionContext) {
@ -519,11 +510,11 @@ impl<'gc, 'a> MovieClip<'gc> {
reader: &mut SwfStream<&'a [u8]>,
tag_len: usize,
) -> DecodeResult {
if self.audio_stream_info.is_some() {
if self.static_data.audio_stream_info.is_some() {
let pos = reader.get_ref().position() as usize;
let data = reader.get_ref().get_ref();
let data = &data[pos..pos + tag_len];
context.audio.preload_sound_stream_block(self.id, data);
context.audio.preload_sound_stream_block(self.id(), data);
}
Ok(())
@ -534,13 +525,14 @@ impl<'gc, 'a> MovieClip<'gc> {
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
reader: &mut SwfStream<&'a [u8]>,
static_data: &mut MovieClipStatic,
_version: u8,
) -> DecodeResult {
let audio_stream_info = reader.read_sound_stream_head()?;
context
.audio
.preload_sound_stream_head(self.id, &audio_stream_info);
self.audio_stream_info = Some(audio_stream_info);
.preload_sound_stream_head(self.id(), &audio_stream_info);
static_data.audio_stream_info = Some(audio_stream_info);
Ok(())
}
@ -778,7 +770,7 @@ impl<'gc, 'a> MovieClip<'gc> {
let id = reader.read_character_id()?;
let num_frames = reader.read_u16()?;
let mut movie_clip =
MovieClip::new_with_data(id, reader.get_ref().position(), tag_len - 4, num_frames);
MovieClip::new_with_data(context.gc_context, id, reader.get_ref().position(), tag_len - 4, num_frames);
movie_clip.preload(context);
@ -803,6 +795,22 @@ impl<'gc, 'a> MovieClip<'gc> {
Ok(())
}
#[inline]
fn frame_label(
&mut self,
_context: &mut UpdateContext<'_, 'gc, '_>,
reader: &mut SwfStream<&'a [u8]>,
tag_len: usize,
cur_frame: FrameNumber,
static_data: &mut MovieClipStatic,
) -> DecodeResult {
let frame_label = reader.read_frame_label(tag_len)?;
if static_data.frame_labels.insert(frame_label.label, cur_frame).is_some() {
log::warn!("Movie clip {}: Duplicated frame label", self.id());
}
Ok(())
}
#[inline]
fn jpeg_tables(
&mut self,
@ -837,6 +845,17 @@ impl<'gc, 'a> MovieClip<'gc> {
ids.remove(&remove_object.depth);
Ok(())
}
#[inline]
fn preload_show_frame(
&mut self,
_context: &mut UpdateContext<'_, 'gc, '_>,
_reader: &mut SwfStream<&'a [u8]>,
cur_frame: &mut FrameNumber,
) -> DecodeResult {
*cur_frame += 1;
Ok(())
}
}
// Control tags
@ -851,7 +870,7 @@ impl<'gc, 'a> MovieClip<'gc> {
// Queue the actions.
// TODO: The reader is actually reading the tag slice at this point (tag_stream.take()),
// so make sure to get the proper offsets. This feels kind of bad.
let start = (self.tag_stream_start + reader.get_ref().position()) as usize;
let start = (self.tag_stream_start() + reader.get_ref().position()) as usize;
let end = start + tag_len;
let slice = crate::tag_utils::SwfSlice {
data: std::sync::Arc::clone(context.swf_data),
@ -992,13 +1011,13 @@ impl<'gc, 'a> MovieClip<'gc> {
context: &mut UpdateContext<'_, 'gc, '_>,
_reader: &mut SwfStream<&'a [u8]>,
) -> DecodeResult {
if let (Some(stream_info), None) = (&self.audio_stream_info, &self.audio_stream) {
if let (Some(stream_info), None) = (&self.static_data.audio_stream_info, &self.audio_stream) {
let slice = crate::tag_utils::SwfSlice {
data: std::sync::Arc::clone(context.swf_data),
start: self.tag_stream_start as usize,
end: self.tag_stream_start as usize + self.tag_stream_len,
start: self.tag_stream_start() as usize,
end: self.tag_stream_start() as usize + self.tag_stream_len(),
};
let audio_stream = context.audio.start_stream(self.id, slice, stream_info);
let audio_stream = context.audio.start_stream(self.id(), slice, stream_info);
self.audio_stream = Some(audio_stream);
}
@ -1018,3 +1037,36 @@ impl<'gc, 'a> MovieClip<'gc> {
Ok(())
}
}
/// Static data shared between all instances of a movie clip.
#[allow(dead_code)]
#[derive(Clone)]
struct MovieClipStatic {
id: CharacterId,
tag_stream_start: u64,
tag_stream_len: usize,
frame_labels: HashMap<String, FrameNumber>,
audio_stream_info: Option<swf::SoundStreamHead>,
total_frames: FrameNumber,
}
impl Default for MovieClipStatic {
fn default() -> Self {
Self {
id: 0,
tag_stream_start: 0,
tag_stream_len: 0,
total_frames: 1,
frame_labels: HashMap::new(),
audio_stream_info: None,
}
}
}
unsafe impl<'gc> gc_arena::Collect for MovieClipStatic {
#[inline]
fn needs_trace() -> bool {
false
}
}

View File

@ -80,7 +80,7 @@ impl<Audio: AudioBackend, Renderer: RenderBackend> Player<Audio, Renderer> {
library: GcCell::allocate(gc_context, Library::new()),
root: GcCell::allocate(
gc_context,
Box::new(MovieClip::new_with_data(0, 0, swf_len, header.num_frames)),
Box::new(MovieClip::new_with_data(gc_context, 0, 0, swf_len, header.num_frames)),
),
}),