avm1: `Button` should not hold write locks on itself when instantiating children.
This fixes a bug where a nested textfield within a button with variable linkages would panic in Ruffle.
This commit is contained in:
parent
354b194b69
commit
825eb34c67
|
@ -104,6 +104,60 @@ impl<'gc> Button<'gc> {
|
||||||
record.color_transform = color_transform.clone();
|
record.color_transform = color_transform.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the state of a button, creating or destroying children as needed.
|
||||||
|
///
|
||||||
|
/// This function instantiates children and thus must not be called whilst
|
||||||
|
/// the caller is holding a write lock on the button data.
|
||||||
|
fn set_state(
|
||||||
|
&self,
|
||||||
|
self_display_object: DisplayObject<'gc>,
|
||||||
|
context: &mut crate::context::UpdateContext<'_, 'gc, '_>,
|
||||||
|
state: ButtonState,
|
||||||
|
) {
|
||||||
|
let movie = self.movie().unwrap();
|
||||||
|
let mut write = self.0.write(context.gc_context);
|
||||||
|
write.state = state;
|
||||||
|
let swf_state = match state {
|
||||||
|
ButtonState::Up => swf::ButtonState::Up,
|
||||||
|
ButtonState::Over => swf::ButtonState::Over,
|
||||||
|
ButtonState::Down => swf::ButtonState::Down,
|
||||||
|
};
|
||||||
|
write.children.clear();
|
||||||
|
|
||||||
|
let mut new_children = Vec::new();
|
||||||
|
|
||||||
|
for record in &write.static_data.read().records {
|
||||||
|
if record.states.contains(&swf_state) {
|
||||||
|
if let Ok(mut child) = context
|
||||||
|
.library
|
||||||
|
.library_for_movie_mut(movie.clone())
|
||||||
|
.instantiate_by_id(record.id, context.gc_context)
|
||||||
|
{
|
||||||
|
child.set_parent(context.gc_context, Some(self_display_object));
|
||||||
|
child.set_matrix(context.gc_context, &record.matrix);
|
||||||
|
child.set_color_transform(
|
||||||
|
context.gc_context,
|
||||||
|
&record.color_transform.clone().into(),
|
||||||
|
);
|
||||||
|
child.set_depth(context.gc_context, record.depth.into());
|
||||||
|
|
||||||
|
new_children.push((child, record.depth.into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(write);
|
||||||
|
|
||||||
|
for (mut child, depth) in new_children {
|
||||||
|
child.post_instantiation(context, child, None, false);
|
||||||
|
child.run_frame(context);
|
||||||
|
self.0
|
||||||
|
.write(context.gc_context)
|
||||||
|
.children
|
||||||
|
.insert(depth, child);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'gc> TDisplayObject<'gc> for Button<'gc> {
|
impl<'gc> TDisplayObject<'gc> for Button<'gc> {
|
||||||
|
@ -138,9 +192,58 @@ impl<'gc> TDisplayObject<'gc> for Button<'gc> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
fn run_frame(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) {
|
||||||
|
let self_display_object = (*self).into();
|
||||||
|
let initialized = self.0.read().initialized;
|
||||||
|
|
||||||
|
// TODO: Move this to post_instantiation.
|
||||||
|
if !initialized {
|
||||||
|
let mut new_children = Vec::new();
|
||||||
|
|
||||||
|
self.0.write(context.gc_context).initialized = true;
|
||||||
|
|
||||||
|
self.set_state(self_display_object, context, ButtonState::Up);
|
||||||
|
|
||||||
|
let read = self.0.read();
|
||||||
|
|
||||||
|
for record in &read.static_data.read().records {
|
||||||
|
if record.states.contains(&swf::ButtonState::HitTest) {
|
||||||
|
match context
|
||||||
|
.library
|
||||||
|
.library_for_movie_mut(read.static_data.read().swf.clone())
|
||||||
|
.instantiate_by_id(record.id, context.gc_context)
|
||||||
|
{
|
||||||
|
Ok(mut child) => {
|
||||||
|
child.set_matrix(context.gc_context, &record.matrix);
|
||||||
|
child.set_parent(context.gc_context, Some(self_display_object));
|
||||||
|
child.set_depth(context.gc_context, record.depth.into());
|
||||||
|
new_children.push((child, record.depth.into()));
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
log::error!(
|
||||||
|
"Button ID {}: could not instantiate child ID {}: {}",
|
||||||
|
read.static_data.read().id,
|
||||||
|
record.id,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(read);
|
||||||
|
|
||||||
|
for (mut child, depth) in new_children {
|
||||||
|
child.post_instantiation(context, child, None, false);
|
||||||
self.0
|
self.0
|
||||||
.write(context.gc_context)
|
.write(context.gc_context)
|
||||||
.run_frame((*self).into(), context)
|
.hit_area
|
||||||
|
.insert(depth, child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for mut child in self.children() {
|
||||||
|
child.run_frame(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, context: &mut RenderContext<'_, 'gc>) {
|
fn render(&self, context: &mut RenderContext<'_, 'gc>) {
|
||||||
|
@ -212,109 +315,19 @@ impl<'gc> TDisplayObject<'gc> for Button<'gc> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.0
|
|
||||||
.write(context.gc_context)
|
|
||||||
.handle_clip_event((*self).into(), context, event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'gc> ButtonData<'gc> {
|
|
||||||
fn set_state(
|
|
||||||
&mut self,
|
|
||||||
self_display_object: DisplayObject<'gc>,
|
|
||||||
context: &mut crate::context::UpdateContext<'_, 'gc, '_>,
|
|
||||||
state: ButtonState,
|
|
||||||
) {
|
|
||||||
self.state = state;
|
|
||||||
let swf_state = match state {
|
|
||||||
ButtonState::Up => swf::ButtonState::Up,
|
|
||||||
ButtonState::Over => swf::ButtonState::Over,
|
|
||||||
ButtonState::Down => swf::ButtonState::Down,
|
|
||||||
};
|
|
||||||
self.children.clear();
|
|
||||||
for record in &self.static_data.read().records {
|
|
||||||
if record.states.contains(&swf_state) {
|
|
||||||
if let Ok(mut child) = context
|
|
||||||
.library
|
|
||||||
.library_for_movie_mut(self.movie())
|
|
||||||
.instantiate_by_id(record.id, context.gc_context)
|
|
||||||
{
|
|
||||||
child.set_parent(context.gc_context, Some(self_display_object));
|
|
||||||
child.set_matrix(context.gc_context, &record.matrix);
|
|
||||||
child.set_color_transform(
|
|
||||||
context.gc_context,
|
|
||||||
&record.color_transform.clone().into(),
|
|
||||||
);
|
|
||||||
child.set_depth(context.gc_context, record.depth.into());
|
|
||||||
child.post_instantiation(context, child, None, false);
|
|
||||||
child.run_frame(context);
|
|
||||||
self.children.insert(record.depth.into(), child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_frame(
|
|
||||||
&mut self,
|
|
||||||
self_display_object: DisplayObject<'gc>,
|
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
||||||
) {
|
|
||||||
// TODO: Move this to post_instantiation.
|
|
||||||
if !self.initialized {
|
|
||||||
self.initialized = true;
|
|
||||||
self.set_state(self_display_object, context, ButtonState::Up);
|
|
||||||
|
|
||||||
for record in &self.static_data.read().records {
|
|
||||||
if record.states.contains(&swf::ButtonState::HitTest) {
|
|
||||||
match context
|
|
||||||
.library
|
|
||||||
.library_for_movie_mut(self.static_data.read().swf.clone())
|
|
||||||
.instantiate_by_id(record.id, context.gc_context)
|
|
||||||
{
|
|
||||||
Ok(mut child) => {
|
|
||||||
{
|
|
||||||
child.set_matrix(context.gc_context, &record.matrix);
|
|
||||||
child.set_parent(context.gc_context, Some(self_display_object));
|
|
||||||
child.set_depth(context.gc_context, record.depth.into());
|
|
||||||
child.post_instantiation(context, child, None, false);
|
|
||||||
}
|
|
||||||
self.hit_area.insert(record.depth.into(), child);
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
log::error!(
|
|
||||||
"Button ID {}: could not instantiate child ID {}: {}",
|
|
||||||
self.static_data.read().id,
|
|
||||||
record.id,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for child in self.children.values_mut() {
|
|
||||||
child.run_frame(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_clip_event(
|
|
||||||
&mut self,
|
|
||||||
self_display_object: DisplayObject<'gc>,
|
|
||||||
context: &mut crate::context::UpdateContext<'_, 'gc, '_>,
|
|
||||||
event: ClipEvent,
|
|
||||||
) -> ClipEventResult {
|
|
||||||
let mut handled = ClipEventResult::NotHandled;
|
let mut handled = ClipEventResult::NotHandled;
|
||||||
|
let self_display_object = (*self).into();
|
||||||
|
let mut 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 cur_state = self.state;
|
let cur_state = write.state;
|
||||||
let new_state = match event {
|
let new_state = match event {
|
||||||
ClipEvent::RollOut => ButtonState::Up,
|
ClipEvent::RollOut => ButtonState::Up,
|
||||||
ClipEvent::RollOver => ButtonState::Over,
|
ClipEvent::RollOver => ButtonState::Over,
|
||||||
ClipEvent::Press => ButtonState::Down,
|
ClipEvent::Press => ButtonState::Down,
|
||||||
ClipEvent::Release => ButtonState::Over,
|
ClipEvent::Release => ButtonState::Over,
|
||||||
ClipEvent::KeyPress { key_code } => {
|
ClipEvent::KeyPress { key_code } => {
|
||||||
handled = self.run_actions(
|
handled = write.run_actions(
|
||||||
context,
|
context,
|
||||||
swf::ButtonActionCondition::KeyPress,
|
swf::ButtonActionCondition::KeyPress,
|
||||||
Some(key_code),
|
Some(key_code),
|
||||||
|
@ -326,20 +339,26 @@ impl<'gc> ButtonData<'gc> {
|
||||||
|
|
||||||
match (cur_state, new_state) {
|
match (cur_state, new_state) {
|
||||||
(ButtonState::Up, ButtonState::Over) => {
|
(ButtonState::Up, ButtonState::Over) => {
|
||||||
self.run_actions(context, swf::ButtonActionCondition::IdleToOverUp, None);
|
write.run_actions(context, swf::ButtonActionCondition::IdleToOverUp, None);
|
||||||
self.play_sound(context, self.static_data.read().up_to_over_sound.as_ref());
|
write.play_sound(context, write.static_data.read().up_to_over_sound.as_ref());
|
||||||
}
|
}
|
||||||
(ButtonState::Over, ButtonState::Up) => {
|
(ButtonState::Over, ButtonState::Up) => {
|
||||||
self.run_actions(context, swf::ButtonActionCondition::OverUpToIdle, None);
|
write.run_actions(context, swf::ButtonActionCondition::OverUpToIdle, None);
|
||||||
self.play_sound(context, self.static_data.read().over_to_up_sound.as_ref());
|
write.play_sound(context, write.static_data.read().over_to_up_sound.as_ref());
|
||||||
}
|
}
|
||||||
(ButtonState::Over, ButtonState::Down) => {
|
(ButtonState::Over, ButtonState::Down) => {
|
||||||
self.run_actions(context, swf::ButtonActionCondition::OverUpToOverDown, None);
|
write.run_actions(context, swf::ButtonActionCondition::OverUpToOverDown, None);
|
||||||
self.play_sound(context, self.static_data.read().over_to_down_sound.as_ref());
|
write.play_sound(
|
||||||
|
context,
|
||||||
|
write.static_data.read().over_to_down_sound.as_ref(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
(ButtonState::Down, ButtonState::Over) => {
|
(ButtonState::Down, ButtonState::Over) => {
|
||||||
self.run_actions(context, swf::ButtonActionCondition::OverDownToOverUp, None);
|
write.run_actions(context, swf::ButtonActionCondition::OverDownToOverUp, None);
|
||||||
self.play_sound(context, self.static_data.read().down_to_over_sound.as_ref());
|
write.play_sound(
|
||||||
|
context,
|
||||||
|
write.static_data.read().down_to_over_sound.as_ref(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
|
@ -351,7 +370,7 @@ impl<'gc> ButtonData<'gc> {
|
||||||
context.action_queue.queue_actions(
|
context.action_queue.queue_actions(
|
||||||
self_display_object,
|
self_display_object,
|
||||||
ActionType::Method {
|
ActionType::Method {
|
||||||
object: self.object.unwrap(),
|
object: write.object.unwrap(),
|
||||||
name,
|
name,
|
||||||
args: vec![],
|
args: vec![],
|
||||||
},
|
},
|
||||||
|
@ -360,11 +379,15 @@ impl<'gc> ButtonData<'gc> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drop(write);
|
||||||
|
|
||||||
self.set_state(self_display_object, context, new_state);
|
self.set_state(self_display_object, context, new_state);
|
||||||
|
|
||||||
handled
|
handled
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> ButtonData<'gc> {
|
||||||
fn play_sound(
|
fn play_sound(
|
||||||
&self,
|
&self,
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
|
Loading…
Reference in New Issue