diff --git a/core/src/avm2/globals/flash/display/simple_button.rs b/core/src/avm2/globals/flash/display/simple_button.rs index e0ed0724e..9b4e70da3 100644 --- a/core/src/avm2/globals/flash/display/simple_button.rs +++ b/core/src/avm2/globals/flash/display/simple_button.rs @@ -274,7 +274,7 @@ pub fn get_track_as_menu<'gc>( /// Implements `trackAsMenu`'s setter pub fn set_track_as_menu<'gc>( - activation: &mut Activation<'_, 'gc>, + _activation: &mut Activation<'_, 'gc>, this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { @@ -283,8 +283,8 @@ pub fn set_track_as_menu<'gc>( .and_then(|this| this.as_avm2_button()) { match args.get_bool(0) { - true => btn.set_button_tracking(&mut activation.context, ButtonTracking::Menu), - false => btn.set_button_tracking(&mut activation.context, ButtonTracking::Push), + true => btn.set_button_tracking(ButtonTracking::Menu), + false => btn.set_button_tracking(ButtonTracking::Push), } } @@ -341,7 +341,7 @@ pub fn get_use_hand_cursor<'gc>( /// Implements `useHandCursor`'s setter pub fn set_use_hand_cursor<'gc>( - activation: &mut Activation<'_, 'gc>, + _activation: &mut Activation<'_, 'gc>, this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { @@ -349,7 +349,7 @@ pub fn set_use_hand_cursor<'gc>( .as_display_object() .and_then(|this| this.as_avm2_button()) { - btn.set_use_hand_cursor(&mut activation.context, args.get_bool(0)); + btn.set_use_hand_cursor(args.get_bool(0)); } Ok(Value::Undefined) diff --git a/core/src/display_object/avm2_button.rs b/core/src/display_object/avm2_button.rs index c29762536..8ee20fbfc 100644 --- a/core/src/display_object/avm2_button.rs +++ b/core/src/display_object/avm2_button.rs @@ -17,9 +17,11 @@ use crate::prelude::*; use crate::tag_utils::{SwfMovie, SwfSlice}; use crate::vminterface::Instantiator; use core::fmt; -use gc_arena::{Collect, GcCell, Mutation}; +use gc_arena::barrier::unlock; +use gc_arena::lock::{Lock, RefLock}; +use gc_arena::{Collect, Gc, Mutation}; use ruffle_render::filters::Filter; -use std::cell::{Ref, RefMut}; +use std::cell::{Cell, Ref, RefCell, RefMut}; use std::sync::Arc; use super::container::dispatch_added_to_stage_event; @@ -28,12 +30,12 @@ use super::interactive::Avm2MousePick; #[derive(Clone, Collect, Copy)] #[collect(no_drop)] -pub struct Avm2Button<'gc>(GcCell<'gc, Avm2ButtonData<'gc>>); +pub struct Avm2Button<'gc>(Gc<'gc, Avm2ButtonData<'gc>>); impl fmt::Debug for Avm2Button<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Avm2Button") - .field("ptr", &self.0.as_ptr()) + .field("ptr", &Gc::as_ptr(self.0)) .finish() } } @@ -41,59 +43,58 @@ impl fmt::Debug for Avm2Button<'_> { #[derive(Clone, Collect)] #[collect(no_drop)] pub struct Avm2ButtonData<'gc> { - base: InteractiveObjectBase<'gc>, + base: RefLock>, - static_data: GcCell<'gc, ButtonStatic>, + static_data: Gc<'gc, ButtonStatic>, /// The current button state to render. - state: ButtonState, + state: Cell, /// The display object tree to render when the button is in the UP state. - up_state: Option>, + up_state: Lock>>, /// The display object tree to render when the button is in the OVER state. - over_state: Option>, + over_state: Lock>>, /// The display object tree to render when the button is in the DOWN state. - down_state: Option>, + down_state: Lock>>, /// The display object tree to use for mouse hit checks. - hit_area: Option>, + hit_area: Lock>>, /// The current tracking mode of this button. - #[collect(require_static)] - tracking: ButtonTracking, + tracking: Cell, /// The class of this button. /// /// If not specified in `SymbolClass`, this will be /// `flash.display.SimpleButton`. - class: Avm2ClassObject<'gc>, + class: Lock>, /// The AVM2 representation of this button. - object: Option>, + object: Lock>>, + + has_focus: Cell, + enabled: Cell, + use_hand_cursor: Cell, /// If this button needs to have it's child states constructed, or not. /// /// All buttons start out unconstructed and have this flag set `true`. /// This flag is consumed during frame construction. - needs_frame_construction: bool, - - has_focus: bool, - enabled: bool, - use_hand_cursor: bool, + needs_frame_construction: Cell, /// Skip the next `run_frame` call. /// /// This flag exists due to a really odd feature of buttons: they run their /// children for one frame before parents can run. Then they go back to the /// normal AVM2 execution order for future frames. - skip_current_frame: bool, + skip_current_frame: Cell, /// When we initially construct a button and run a nested frame, /// we run the framescripts for our states in a different order then /// we do for all other framescript runs. - weird_framescript_order: bool, + weird_framescript_order: Cell, } impl<'gc> Avm2Button<'gc> { @@ -103,39 +104,42 @@ impl<'gc> Avm2Button<'gc> { context: &mut UpdateContext<'_, 'gc>, construct_blank_states: bool, ) -> Self { - let static_data = ButtonStatic { - swf: source_movie.movie.clone(), - id: button.id, - records: button.records.clone(), - up_to_over_sound: None, - over_to_down_sound: None, - down_to_over_sound: None, - over_to_up_sound: None, - }; - - Avm2Button(GcCell::new( - context.gc_context, + Avm2Button(Gc::new( + context.gc(), Avm2ButtonData { base: Default::default(), - static_data: GcCell::new(context.gc_context, static_data), - state: self::ButtonState::Up, - hit_area: None, - up_state: None, - over_state: None, - down_state: None, - class: context.avm2.classes().simplebutton, - object: None, - needs_frame_construction: construct_blank_states, - tracking: if button.is_track_as_menu { + static_data: Gc::new( + context.gc(), + ButtonStatic { + swf: source_movie.movie.clone(), + id: button.id, + cell: RefCell::new(ButtonStaticMut { + records: button.records.clone(), + up_to_over_sound: None, + over_to_down_sound: None, + down_to_over_sound: None, + over_to_up_sound: None, + }), + }, + ), + state: Cell::new(self::ButtonState::Up), + hit_area: Lock::new(None), + up_state: Lock::new(None), + over_state: Lock::new(None), + down_state: Lock::new(None), + class: Lock::new(context.avm2.classes().simplebutton), + object: Lock::new(None), + needs_frame_construction: Cell::new(construct_blank_states), + tracking: Cell::new(if button.is_track_as_menu { ButtonTracking::Menu } else { ButtonTracking::Push - }, - has_focus: false, - enabled: true, - use_hand_cursor: true, - skip_current_frame: false, - weird_framescript_order: false, + }), + has_focus: Cell::new(false), + enabled: Cell::new(true), + use_hand_cursor: Cell::new(true), + skip_current_frame: Cell::new(false), + weird_framescript_order: Cell::new(false), }, )) } @@ -152,9 +156,8 @@ impl<'gc> Avm2Button<'gc> { Self::from_swf_tag(&button_record, &movie.into(), context, false) } - pub fn set_sounds(self, gc_context: &Mutation<'gc>, sounds: swf::ButtonSounds) { - let button = self.0.write(gc_context); - let mut static_data = button.static_data.write(gc_context); + pub fn set_sounds(self, sounds: swf::ButtonSounds) { + let mut static_data = self.0.static_data.cell.borrow_mut(); static_data.up_to_over_sound = sounds.up_to_over_sound; static_data.over_to_down_sound = sounds.over_to_down_sound; static_data.down_to_over_sound = sounds.down_to_over_sound; @@ -163,9 +166,8 @@ impl<'gc> Avm2Button<'gc> { /// Handles the ancient DefineButtonCxform SWF tag. /// Set the color transform for all children of each state. - pub fn set_colors(self, gc_context: &Mutation<'gc>, color_transforms: &[swf::ColorTransform]) { - let button = self.0.write(gc_context); - let mut static_data = button.static_data.write(gc_context); + pub fn set_colors(self, color_transforms: &[swf::ColorTransform]) { + let mut static_data = self.0.static_data.cell.borrow_mut(); // This tag isn't documented well in SWF19. It is only used in very old SWF<=2 content. // It applies color transforms to every character in a button, in sequence(?). @@ -196,9 +198,9 @@ impl<'gc> Avm2Button<'gc> { let sprite_class = context.avm2.classes().sprite; let mut children = Vec::new(); - let static_data = self.0.read().static_data; + let static_data = self.0.static_data; - for record in static_data.read().records.iter() { + for record in static_data.cell.borrow().records.iter() { if record.states.contains(swf_state) { match context .library @@ -206,14 +208,14 @@ impl<'gc> Avm2Button<'gc> { .instantiate_by_id(record.id, context.gc_context) { Ok(child) => { - child.set_matrix(context.gc_context, record.matrix.into()); - child.set_depth(context.gc_context, record.depth.into()); + child.set_matrix(context.gc(), record.matrix.into()); + child.set_depth(context.gc(), record.depth.into()); if swf_state != swf::ButtonState::HIT_TEST { - child.set_color_transform(context.gc_context, record.color_transform); - child.set_blend_mode(context.gc_context, record.blend_mode.into()); + child.set_color_transform(context.gc(), record.color_transform); + child.set_blend_mode(context.gc(), record.blend_mode.into()); child.set_filters( - context.gc_context, + context.gc(), record.filters.iter().map(Filter::from).collect(), ); } @@ -223,7 +225,7 @@ impl<'gc> Avm2Button<'gc> { Err(error) => { tracing::error!( "Button ID {}: could not instantiate child ID {}: {}", - static_data.read().id, + static_data.id, record.id, error ); @@ -232,7 +234,7 @@ impl<'gc> Avm2Button<'gc> { } } - self.invalidate_cached_bitmap(context.gc_context); + self.invalidate_cached_bitmap(context.gc()); // We manually call `construct_frame` for `child` and `state_sprite` - normally // this would be done in the `DisplayObject` constructor, but SimpleButton does @@ -248,8 +250,8 @@ impl<'gc> Avm2Button<'gc> { (child, false) } else { - let state_sprite = MovieClip::new(movie, context.gc_context); - state_sprite.set_avm2_class(context.gc_context, Some(sprite_class)); + let state_sprite = MovieClip::new(movie, context.gc()); + state_sprite.set_avm2_class(context.gc(), Some(sprite_class)); state_sprite.set_parent(context, Some(self.into())); catchup_display_object_to_frame(context, state_sprite.into()); @@ -274,24 +276,23 @@ impl<'gc> Avm2Button<'gc> { /// Get the rendered state of the button. pub fn state(self) -> ButtonState { - self.0.read().state + self.0.state.get() } /// Change the rendered state of the button. pub fn set_state(self, context: &mut UpdateContext<'_, 'gc>, state: ButtonState) { - self.invalidate_cached_bitmap(context.gc_context); - self.0.write(context.gc_context).state = state; - let button = self.0.read(); - if let Some(state) = button.up_state { + self.invalidate_cached_bitmap(context.gc()); + + if let Some(state) = self.0.up_state.get() { state.set_parent(context, None); } - if let Some(state) = button.over_state { + if let Some(state) = self.0.over_state.get() { state.set_parent(context, None); } - if let Some(state) = button.down_state { + if let Some(state) = self.0.down_state.get() { state.set_parent(context, None); } - if let Some(state) = button.hit_area { + if let Some(state) = self.0.hit_area.get() { state.set_parent(context, None); } if let Some(state) = self.get_state_child(state.into()) { @@ -302,10 +303,10 @@ impl<'gc> Avm2Button<'gc> { /// Get the display object that represents a particular button state. pub fn get_state_child(self, state: swf::ButtonState) -> Option> { match state { - swf::ButtonState::UP => self.0.read().up_state, - swf::ButtonState::OVER => self.0.read().over_state, - swf::ButtonState::DOWN => self.0.read().down_state, - swf::ButtonState::HIT_TEST => self.0.read().hit_area, + swf::ButtonState::UP => self.0.up_state.get(), + swf::ButtonState::OVER => self.0.over_state.get(), + swf::ButtonState::DOWN => self.0.down_state.get(), + swf::ButtonState::HIT_TEST => self.0.hit_area.get(), _ => None, } } @@ -319,13 +320,14 @@ impl<'gc> Avm2Button<'gc> { ) { let child_was_on_stage = child.map(|c| c.is_on_stage(context)).unwrap_or(false); let old_state_child = self.get_state_child(state); - let is_cur_state = swf::ButtonState::from(self.0.read().state) == state; + let is_cur_state = swf::ButtonState::from(self.0.state.get()) == state; + let write = Gc::write(context.gc(), self.0); match state { - swf::ButtonState::UP => self.0.write(context.gc_context).up_state = child, - swf::ButtonState::OVER => self.0.write(context.gc_context).over_state = child, - swf::ButtonState::DOWN => self.0.write(context.gc_context).down_state = child, - swf::ButtonState::HIT_TEST => self.0.write(context.gc_context).hit_area = child, + swf::ButtonState::UP => unlock!(write, Avm2ButtonData, up_state).set(child), + swf::ButtonState::OVER => unlock!(write, Avm2ButtonData, over_state).set(child), + swf::ButtonState::DOWN => unlock!(write, Avm2ButtonData, down_state).set(child), + swf::ButtonState::HIT_TEST => unlock!(write, Avm2ButtonData, hit_area).set(child), _ => (), } @@ -366,64 +368,61 @@ impl<'gc> Avm2Button<'gc> { } pub fn enabled(self) -> bool { - self.0.read().enabled + self.0.enabled.get() } pub fn set_enabled(self, context: &mut UpdateContext<'_, 'gc>, enabled: bool) { - self.0.write(context.gc_context).enabled = enabled; + self.0.enabled.set(enabled); if !enabled { self.set_state(context, ButtonState::Up); } } pub fn use_hand_cursor(self) -> bool { - self.0.read().use_hand_cursor + self.0.use_hand_cursor.get() } - pub fn set_use_hand_cursor(self, context: &mut UpdateContext<'_, 'gc>, use_hand_cursor: bool) { - self.0.write(context.gc_context).use_hand_cursor = use_hand_cursor; + pub fn set_use_hand_cursor(self, use_hand_cursor: bool) { + self.0.use_hand_cursor.set(use_hand_cursor); } pub fn button_tracking(self) -> ButtonTracking { - self.0.read().tracking + self.0.tracking.get() } - pub fn set_button_tracking( - self, - context: &mut UpdateContext<'_, 'gc>, - tracking: ButtonTracking, - ) { - self.0.write(context.gc_context).tracking = tracking; + pub fn set_button_tracking(self, tracking: ButtonTracking) { + self.0.tracking.set(tracking); } pub fn set_avm2_class(self, mc: &Mutation<'gc>, class: Avm2ClassObject<'gc>) { - self.0.write(mc).class = class; + unlock!(Gc::write(mc, self.0), Avm2ButtonData, class).set(class); } } impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> { fn base(&self) -> Ref> { - Ref::map(self.0.read(), |r| &r.base.base) + Ref::map(self.0.base.borrow(), |r| &r.base) } fn base_mut<'a>(&'a self, mc: &Mutation<'gc>) -> RefMut<'a, DisplayObjectBase<'gc>> { - RefMut::map(self.0.write(mc), |w| &mut w.base.base) + let data = unlock!(Gc::write(mc, self.0), Avm2ButtonData, base); + RefMut::map(data.borrow_mut(), |w| &mut w.base) } - fn instantiate(&self, gc_context: &Mutation<'gc>) -> DisplayObject<'gc> { - Self(GcCell::new(gc_context, self.0.read().clone())).into() + fn instantiate(&self, mc: &Mutation<'gc>) -> DisplayObject<'gc> { + Self(Gc::new(mc, (*self.0).clone())).into() } fn as_ptr(&self) -> *const DisplayObjectPtr { - self.0.as_ptr() as *const DisplayObjectPtr + Gc::as_ptr(self.0) as *const DisplayObjectPtr } fn id(&self) -> CharacterId { - self.0.read().static_data.read().id + self.0.static_data.id } fn movie(&self) -> Arc { - self.0.read().static_data.read().swf.clone() + self.0.static_data.swf.clone() } fn post_instantiation( @@ -437,59 +436,49 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> { } fn enter_frame(&self, context: &mut UpdateContext<'_, 'gc>) { - let hit_area = self.0.read().hit_area; - if let Some(hit_area) = hit_area { + if let Some(hit_area) = self.0.hit_area.get() { hit_area.enter_frame(context); } - let up_state = self.0.read().up_state; - if let Some(up_state) = up_state { + if let Some(up_state) = self.0.up_state.get() { up_state.enter_frame(context); } - let down_state = self.0.read().down_state; - if let Some(down_state) = down_state { + if let Some(down_state) = self.0.down_state.get() { down_state.enter_frame(context); } - let over_state = self.0.read().over_state; - if let Some(over_state) = over_state { + if let Some(over_state) = self.0.over_state.get() { over_state.enter_frame(context); } } fn construct_frame(&self, context: &mut UpdateContext<'_, 'gc>) { - let hit_area = self.0.read().hit_area; - if let Some(hit_area) = hit_area { + if let Some(hit_area) = self.0.hit_area.get() { hit_area.construct_frame(context); } - let up_state = self.0.read().up_state; - if let Some(up_state) = up_state { + if let Some(up_state) = self.0.up_state.get() { up_state.construct_frame(context); } - let down_state = self.0.read().down_state; - if let Some(down_state) = down_state { + if let Some(down_state) = self.0.down_state.get() { down_state.construct_frame(context); } - let over_state = self.0.read().over_state; - if let Some(over_state) = over_state { + if let Some(over_state) = self.0.over_state.get() { over_state.construct_frame(context); } - let needs_avm2_construction = self.0.read().object.is_none(); - let class = self.0.read().class; + let needs_avm2_construction = self.0.object.get().is_none(); + let class = self.0.class.get(); - let needs_frame_construction = self.0.read().needs_frame_construction; - if needs_frame_construction { + if self.0.needs_frame_construction.get() { if needs_avm2_construction { + let object_cell = unlock!(Gc::write(context.gc(), self.0), Avm2ButtonData, object); let mut activation = Avm2Activation::from_nothing(context.reborrow()); match Avm2StageObject::for_display_object(&mut activation, (*self).into(), class) { - Ok(object) => { - self.0.write(activation.context.gc_context).object = Some(object.into()) - } + Ok(object) => object_cell.set(Some(object.into())), Err(e) => tracing::error!("Got {} when constructing AVM2 side of button", e), }; if !self.placed_by_script() { @@ -500,7 +489,7 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> { } // Prevent re-entrantly constructing this button (since we may run a nested frame here) - self.0.write(context.gc_context).needs_frame_construction = false; + self.0.needs_frame_construction.set(false); let (up_state, up_should_fire) = self.create_state(context, swf::ButtonState::UP); let (over_state, over_should_fire) = self.create_state(context, swf::ButtonState::OVER); let (down_state, down_should_fire) = self.create_state(context, swf::ButtonState::DOWN); @@ -520,14 +509,13 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> { objs.iter().any(|display| display.as_movie_clip().is_some()) }; - let mut write = self.0.write(context.gc_context); - write.up_state = Some(up_state); - write.over_state = Some(over_state); - write.down_state = Some(down_state); - write.hit_area = Some(hit_area); - write.skip_current_frame = true; - - drop(write); + let write = Gc::write(context.gc(), self.0); + unlock!(write, Avm2ButtonData, up_state).set(Some(up_state)); + unlock!(write, Avm2ButtonData, over_state).set(Some(over_state)); + unlock!(write, Avm2ButtonData, down_state).set(Some(down_state)); + unlock!(write, Avm2ButtonData, hit_area).set(Some(hit_area)); + write.skip_current_frame.set(true); + write.needs_frame_construction.set(false); let mut fire_state_events = |should_fire, state: DisplayObject<'gc>| { if should_fire { @@ -565,7 +553,7 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> { self.set_state(context, ButtonState::Up); if has_movie_clip_state && self.movie().version() > 9 { - self.0.write(context.gc_context).weird_framescript_order = true; + self.0.weird_framescript_order.set(true); let stage = context.stage; stage.construct_frame(context); @@ -575,8 +563,7 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> { stage.exit_frame(context); } - let avm2_object = self.0.read().object; - if let Some(avm2_object) = avm2_object { + if let Some(avm2_object) = self.0.object.get() { let mut activation = Avm2Activation::from_nothing(context.reborrow()); if let Err(e) = class.call_native_init(avm2_object.into(), &[], &mut activation) { @@ -592,50 +579,37 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> { } fn run_frame_scripts(self, context: &mut UpdateContext<'_, 'gc>) { - if self.0.read().weird_framescript_order { - self.0.write(context.gc_context).weird_framescript_order = false; - let up_state = self.0.read().up_state; - if let Some(up_state) = up_state { + if self.0.weird_framescript_order.take() { + if let Some(up_state) = self.0.up_state.get() { up_state.run_frame_scripts(context); } - let over_state = self.0.read().over_state; - if let Some(over_state) = over_state { + if let Some(over_state) = self.0.over_state.get() { over_state.run_frame_scripts(context); } - let down_state = self.0.read().down_state; - if let Some(down_state) = down_state { + if let Some(down_state) = self.0.down_state.get() { down_state.run_frame_scripts(context); } - let hit_area = self.0.read().hit_area; - if let Some(hit_area) = hit_area { + if let Some(hit_area) = self.0.hit_area.get() { hit_area.run_frame_scripts(context); } } else { - let hit_area = self.0.read().hit_area; - if let Some(hit_area) = hit_area { + if let Some(hit_area) = self.0.hit_area.get() { hit_area.run_frame_scripts(context); } - - let up_state = self.0.read().up_state; - if let Some(up_state) = up_state { + if let Some(up_state) = self.0.up_state.get() { up_state.run_frame_scripts(context); } - - let down_state = self.0.read().down_state; - if let Some(down_state) = down_state { + if let Some(down_state) = self.0.down_state.get() { down_state.run_frame_scripts(context); } - - let over_state = self.0.read().over_state; - if let Some(over_state) = over_state { + if let Some(over_state) = self.0.over_state.get() { over_state.run_frame_scripts(context); } } } fn render_self(&self, context: &mut RenderContext<'_, 'gc>) { - let state = self.0.read().state; - let current_state = self.get_state_child(state.into()); + let current_state = self.get_state_child(self.0.state.get().into()); if let Some(state) = current_state { state.render(context); @@ -664,8 +638,7 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> { let mut bounds = *matrix * self.self_bounds(); // Add the bounds of the child, dictated by current state - let state = self.0.read().state; - if let Some(child) = self.get_state_child(state.into()) { + if let Some(child) = self.get_state_child(self.0.state.get().into()) { let matrix = *matrix * *child.base().matrix(); let child_bounds = child.bounds_with_transform(&matrix); bounds = bounds.union(&child_bounds); @@ -682,8 +655,7 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> { ) -> Rectangle { let mut bounds = *matrix * self.self_bounds(); - let state = self.0.read().state; - if let Some(child) = self.get_state_child(state.into()) { + if let Some(child) = self.get_state_child(self.0.state.get().into()) { let matrix = *matrix * *child.base().matrix(); bounds = bounds.union(&child.render_bounds_with_transform(&matrix, true, view_matrix)); } @@ -706,8 +678,7 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> { options: HitTestOptions, ) -> bool { if !options.contains(HitTestOptions::SKIP_INVISIBLE) || self.visible() { - let state = self.0.read().state; - if let Some(child) = self.get_state_child(state.into()) { + if let Some(child) = self.get_state_child(self.0.state.get().into()) { //TODO: the if below should probably always be taken, why does the hit area // sometimes have a parent? let mut point = point; @@ -731,14 +702,15 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> { fn object2(&self) -> Avm2Value<'gc> { self.0 - .read() .object + .get() .map(Avm2Value::from) .unwrap_or(Avm2Value::Null) } fn set_object2(&self, context: &mut UpdateContext<'_, 'gc>, to: Avm2Object<'gc>) { - self.0.write(context.gc_context).object = Some(to); + let write = Gc::write(context.gc(), self.0); + unlock!(write, Avm2ButtonData, object).set(Some(to)); } fn as_avm2_button(&self) -> Option { @@ -750,8 +722,7 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> { } fn allow_as_mask(&self) -> bool { - let state = self.0.read().state; - let current_state = self.get_state_child(state.into()); + let current_state = self.get_state_child(self.0.state.get().into()); if let Some(current_state) = current_state.and_then(|cs| cs.as_container()) { current_state.is_empty() @@ -766,21 +737,21 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> { fn on_focus_changed( &self, - context: &mut UpdateContext<'_, 'gc>, + _context: &mut UpdateContext<'_, 'gc>, focused: bool, _other: Option>, ) { - self.0.write(context.gc_context).has_focus = focused; + self.0.has_focus.set(focused); } } impl<'gc> TInteractiveObject<'gc> for Avm2Button<'gc> { fn raw_interactive(&self) -> Ref> { - Ref::map(self.0.read(), |r| &r.base) + self.0.base.borrow() } fn raw_interactive_mut(&self, mc: &Mutation<'gc>) -> RefMut> { - RefMut::map(self.0.write(mc), |w| &mut w.base) + unlock!(Gc::write(mc, self.0), Avm2ButtonData, base).borrow_mut() } fn as_displayobject(self) -> DisplayObject<'gc> { @@ -809,8 +780,7 @@ impl<'gc> TInteractiveObject<'gc> for Avm2Button<'gc> { event: ClipEvent<'gc>, ) -> ClipEventResult { if event.propagates() { - let state = self.0.read().state; - let current_state = self.get_state_child(state.into()); + let current_state = self.get_state_child(self.0.state.get().into()); if let Some(current_state) = current_state.and_then(|s| s.as_interactive()) { if current_state.handle_clip_event(context, event) == ClipEventResult::Handled { @@ -827,11 +797,8 @@ impl<'gc> TInteractiveObject<'gc> for Avm2Button<'gc> { context: &mut UpdateContext<'_, 'gc>, event: ClipEvent<'gc>, ) -> ClipEventResult { - let write = self.0.write(context.gc_context); - // Translate the clip event to a button event, based on how the button state changes. - let static_data = write.static_data; - let static_data = static_data.read(); + let static_data = self.0.static_data.cell.borrow(); let (new_state, sound) = match event { ClipEvent::DragOut { .. } => (ButtonState::Over, None), ClipEvent::DragOver { .. } => (ButtonState::Down, None), @@ -846,9 +813,8 @@ impl<'gc> TInteractiveObject<'gc> for Avm2Button<'gc> { _ => return ClipEventResult::NotHandled, }; - write.play_sound(context, sound); - let old_state = write.state; - drop(write); + self.0.play_sound(context, sound); + let old_state = self.0.state.get(); if old_state != new_state { self.set_state(context, new_state); @@ -864,8 +830,7 @@ impl<'gc> TInteractiveObject<'gc> for Avm2Button<'gc> { ) -> Avm2MousePick<'gc> { // The button is hovered if the mouse is over any child nodes. if self.visible() && self.mouse_enabled() { - let state = self.0.read().state; - let state_child = self.get_state_child(state.into()); + let state_child = self.get_state_child(self.0.state.get().into()); if let Some(state_child) = state_child { let mouse_pick = state_child @@ -878,8 +843,7 @@ impl<'gc> TInteractiveObject<'gc> for Avm2Button<'gc> { }; } - let hit_area = self.0.read().hit_area; - if let Some(hit_area) = hit_area { + if let Some(hit_area) = self.0.hit_area.get() { //TODO: the if below should probably always be taken, why does the hit area // sometimes have a parent? if hit_area.parent().is_none() { @@ -922,17 +886,21 @@ impl<'gc> Avm2ButtonData<'gc> { } fn movie(&self) -> Arc { - self.static_data.read().swf.clone() + self.static_data.swf.clone() } } /// Static data shared between all instances of a button. -#[allow(dead_code)] -#[derive(Clone, Debug, Collect)] +#[derive(Collect, Debug)] #[collect(require_static)] struct ButtonStatic { swf: Arc, id: CharacterId, + cell: RefCell, +} + +#[derive(Debug)] +struct ButtonStaticMut { records: Vec, /// The sounds to play on state changes for this button. diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index b699f1d4a..f9f73a1e1 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -3835,7 +3835,7 @@ impl<'gc, 'a> MovieClipData<'gc> { button.set_sounds(button_sounds); } Some(Character::Avm2Button(button)) => { - button.set_sounds(context.gc_context, button_sounds); + button.set_sounds(button_sounds); } Some(_) => { tracing::warn!(