avm2: Use inner mutability instead of `GcCell` in `Avm2Button`

This commit is contained in:
Moulins 2024-01-18 02:49:13 +01:00 committed by TÖRÖK Attila
parent d1cbe46e25
commit e014d7c023
3 changed files with 166 additions and 198 deletions

View File

@ -274,7 +274,7 @@ pub fn get_track_as_menu<'gc>(
/// Implements `trackAsMenu`'s setter /// Implements `trackAsMenu`'s setter
pub fn set_track_as_menu<'gc>( pub fn set_track_as_menu<'gc>(
activation: &mut Activation<'_, 'gc>, _activation: &mut Activation<'_, 'gc>,
this: Object<'gc>, this: Object<'gc>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
@ -283,8 +283,8 @@ pub fn set_track_as_menu<'gc>(
.and_then(|this| this.as_avm2_button()) .and_then(|this| this.as_avm2_button())
{ {
match args.get_bool(0) { match args.get_bool(0) {
true => btn.set_button_tracking(&mut activation.context, ButtonTracking::Menu), true => btn.set_button_tracking(ButtonTracking::Menu),
false => btn.set_button_tracking(&mut activation.context, ButtonTracking::Push), false => btn.set_button_tracking(ButtonTracking::Push),
} }
} }
@ -341,7 +341,7 @@ pub fn get_use_hand_cursor<'gc>(
/// Implements `useHandCursor`'s setter /// Implements `useHandCursor`'s setter
pub fn set_use_hand_cursor<'gc>( pub fn set_use_hand_cursor<'gc>(
activation: &mut Activation<'_, 'gc>, _activation: &mut Activation<'_, 'gc>,
this: Object<'gc>, this: Object<'gc>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
@ -349,7 +349,7 @@ pub fn set_use_hand_cursor<'gc>(
.as_display_object() .as_display_object()
.and_then(|this| this.as_avm2_button()) .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) Ok(Value::Undefined)

View File

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

View File

@ -3835,7 +3835,7 @@ impl<'gc, 'a> MovieClipData<'gc> {
button.set_sounds(button_sounds); button.set_sounds(button_sounds);
} }
Some(Character::Avm2Button(button)) => { Some(Character::Avm2Button(button)) => {
button.set_sounds(context.gc_context, button_sounds); button.set_sounds(button_sounds);
} }
Some(_) => { Some(_) => {
tracing::warn!( tracing::warn!(