diff --git a/core/src/avm1/activation.rs b/core/src/avm1/activation.rs index 93de73e7b..ce8039728 100644 --- a/core/src/avm1/activation.rs +++ b/core/src/avm1/activation.rs @@ -12,7 +12,7 @@ use crate::display_object::{DisplayObject, MovieClip, TDisplayObject, TDisplayOb use crate::ecma_conversions::{f64_to_wrapping_i32, f64_to_wrapping_u32}; use crate::loader::MovieLoaderVMData; use crate::string::{AvmString, SwfStrExt as _, WStr, WString}; -use crate::tag_utils::SwfSlice; +use crate::tag_utils::{SwfPosition, SwfSlice}; use crate::vminterface::Instantiator; use crate::{avm_error, avm_warn}; use gc_arena::{Gc, GcCell, MutationContext}; @@ -25,6 +25,7 @@ use std::cmp::min; use std::fmt; use swf::avm1::read::Reader; use swf::avm1::types::*; +use swf::extensions::ReadSwfExt; use url::form_urlencoded; use super::object_reference::MovieClipReference; @@ -480,7 +481,7 @@ impl<'a, 'gc> Activation<'a, 'gc> { Action::CastOp => self.action_cast_op(), Action::CharToAscii => self.action_char_to_ascii(), Action::CloneSprite => self.action_clone_sprite(), - Action::ConstantPool(action) => self.action_constant_pool(action), + Action::ConstantPool(action) => self.action_constant_pool(action, data, reader), Action::Decrement => self.action_decrement(), Action::DefineFunction(action) => self.action_define_function(action.into(), data), Action::DefineFunction2(action) => self.action_define_function(action, data), @@ -863,21 +864,42 @@ impl<'a, 'gc> Activation<'a, 'gc> { fn action_constant_pool( &mut self, action: ConstantPool, + data: &SwfSlice, + reader: &mut Reader, ) -> Result, Error<'gc>> { - let constants = action - .strings - .iter() - .map(|s| { - self.context - .interner - .intern_wstr(self.context.gc_context, s.decode(self.encoding())) - .into() - }) - .collect(); + let current_pos = reader.pos(data.movie.data()); + let swf_position = SwfPosition::new(data.movie.clone(), current_pos); + + let const_pool_cache = self.context.avm1.constant_pool_cache(); + + let constants = if let Some(constants) = const_pool_cache.get(&swf_position) { + *constants + } else { + let constants: Vec<_> = action + .strings + .iter() + .map(|s| { + self.context + .interner + .intern_wstr(self.context.gc_context, s.decode(self.encoding())) + .into() + }) + .collect(); + + let consts = Gc::new(self.context.gc_context, constants); + + if consts.len() > 1000 { + self.context + .avm1 + .constant_pool_cache() + .insert(swf_position, consts); + } + + consts + }; + + self.context.avm1.set_constant_pool(constants); - self.context - .avm1 - .set_constant_pool(Gc::new(self.context.gc_context, constants)); self.set_constant_pool(self.context.avm1.constant_pool()); Ok(FrameControl::Continue) diff --git a/core/src/avm1/runtime.rs b/core/src/avm1/runtime.rs index 84b3311eb..a20b85ba2 100644 --- a/core/src/avm1/runtime.rs +++ b/core/src/avm1/runtime.rs @@ -10,10 +10,11 @@ use crate::context::{GcContext, UpdateContext}; use crate::frame_lifecycle::FramePhase; use crate::prelude::*; use crate::string::AvmString; -use crate::tag_utils::SwfSlice; +use crate::tag_utils::{SwfPosition, SwfSlice}; use crate::{avm1, avm_debug}; use gc_arena::{Collect, Gc, MutationContext}; use std::borrow::Cow; +use std::collections::HashMap; use swf::avm1::read::Reader; use tracing::instrument; @@ -27,6 +28,8 @@ pub struct Avm1<'gc> { /// don't close over the constant pool they were defined with. constant_pool: Gc<'gc, Vec>>, + constant_pool_cache: HashMap>>>, + /// The global scope (pre-allocated so that it can be reused by fresh `Activation`s). global_scope: Gc<'gc, Scope<'gc>>, @@ -80,6 +83,7 @@ impl<'gc> Avm1<'gc> { Self { player_version, constant_pool: Gc::new(gc_context, vec![]), + constant_pool_cache: HashMap::new(), global_scope: Gc::new(gc_context, Scope::from_global_object(globals)), prototypes, broadcaster_functions, @@ -363,6 +367,10 @@ impl<'gc> Avm1<'gc> { self.constant_pool = constant_pool; } + pub fn constant_pool_cache(&mut self) -> &mut HashMap>>> { + &mut self.constant_pool_cache + } + /// DisplayObject property map. pub fn display_properties(&self) -> &stage_object::DisplayPropertyMap<'gc> { &self.display_properties diff --git a/core/src/tag_utils.rs b/core/src/tag_utils.rs index 956dae266..0e6cffed3 100644 --- a/core/src/tag_utils.rs +++ b/core/src/tag_utils.rs @@ -1,4 +1,5 @@ use gc_arena::Collect; +use std::hash::{Hash, Hasher}; use std::sync::Arc; use swf::{CharacterId, Fixed8, HeaderExt, Rectangle, TagCode, Twips}; use thiserror::Error; @@ -409,6 +410,34 @@ impl SwfSlice { } } +#[derive(Collect)] +#[collect(no_drop)] +pub struct SwfPosition { + pub movie: Arc, + pub pos: usize, +} + +impl SwfPosition { + pub fn new(movie: Arc, pos: usize) -> Self { + SwfPosition { movie, pos } + } +} + +impl PartialEq for SwfPosition { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.movie, &other.movie) && self.pos == other.pos + } +} + +impl Eq for SwfPosition {} + +impl Hash for SwfPosition { + fn hash(&self, state: &mut H) { + self.pos.hash(state); + Arc::as_ptr(&self.movie).hash(state); + } +} + /// Decode tags from a SWF stream reader. /// /// The given `tag_callback` will be called for each decoded tag. It will be