core: Clean up UpdateContext creation

Added Player::mutate_with_update_context, which takes a closure
and passes it an UpdateContext.
This commit is contained in:
Mike Welsh 2019-12-08 10:49:23 -08:00
parent 30ecbd0ecc
commit 12c1bf7cf1
4 changed files with 108 additions and 209 deletions

View File

@ -480,6 +480,7 @@ mod tests {
renderer: &mut NullRenderer::new(), renderer: &mut NullRenderer::new(),
swf_data: &mut Arc::new(vec![]), swf_data: &mut Arc::new(vec![]),
system_prototypes: avm.prototypes().clone(), system_prototypes: avm.prototypes().clone(),
mouse_hovered_object: None,
}; };
let object = ScriptObject::object(gc_context, Some(avm.prototypes().object)).into(); let object = ScriptObject::object(gc_context, Some(avm.prototypes().object)).into();

View File

@ -45,6 +45,7 @@ where
renderer: &mut NullRenderer::new(), renderer: &mut NullRenderer::new(),
swf_data: &mut Arc::new(vec![]), swf_data: &mut Arc::new(vec![]),
system_prototypes: avm.prototypes().clone(), system_prototypes: avm.prototypes().clone(),
mouse_hovered_object: None,
}; };
let globals = avm.global_object_cell(); let globals = avm.global_object_cell();

View File

@ -85,6 +85,9 @@ pub struct UpdateContext<'a, 'gc, 'gc_context> {
/// The current set of system-specified prototypes to use when constructing /// The current set of system-specified prototypes to use when constructing
/// new built-in objects. /// new built-in objects.
pub system_prototypes: avm1::SystemPrototypes<'gc>, pub system_prototypes: avm1::SystemPrototypes<'gc>,
/// The display object that the mouse is currently hovering over.
pub mouse_hovered_object: Option<DisplayObject<'gc>>,
} }
/// A queued ActionScript call. /// A queued ActionScript call.

View File

@ -28,13 +28,15 @@ struct GcRoot<'gc>(GcCell<'gc, GcRootData<'gc>>);
struct GcRootData<'gc> { struct GcRootData<'gc> {
library: Library<'gc>, library: Library<'gc>,
root: DisplayObject<'gc>, root: DisplayObject<'gc>,
mouse_hover_node: Option<DisplayObject<'gc>>, // TODO: Remove GcCell wrapped inside GcCell. mouse_hovered_object: Option<DisplayObject<'gc>>, // TODO: Remove GcCell wrapped inside GcCell.
avm: Avm1<'gc>, avm: Avm1<'gc>,
action_queue: ActionQueue<'gc>, action_queue: ActionQueue<'gc>,
} }
impl<'gc> GcRootData<'gc> { impl<'gc> GcRootData<'gc> {
fn get( /// Splits out parameters for creating an `UpdateContext`
/// (because we can borrow fields of `self` independently)
fn update_context_params(
&mut self, &mut self,
) -> ( ) -> (
DisplayObject<'gc>, DisplayObject<'gc>,
@ -188,7 +190,7 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
header.num_frames, header.num_frames,
) )
.into(), .into(),
mouse_hover_node: None, mouse_hovered_object: None,
avm: Avm1::new(gc_context, NEWEST_PLAYER_VERSION), avm: Avm1::new(gc_context, NEWEST_PLAYER_VERSION),
action_queue: ActionQueue::new(), action_queue: ActionQueue::new(),
}, },
@ -303,7 +305,6 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
pub fn handle_event(&mut self, event: PlayerEvent) { pub fn handle_event(&mut self, event: PlayerEvent) {
let mut needs_render = false; let mut needs_render = false;
let player_version = self.player_version;
// Update mouse position from mouse events. // Update mouse position from mouse events.
if let PlayerEvent::MouseMove { x, y } if let PlayerEvent::MouseMove { x, y }
@ -317,68 +318,23 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
} }
} }
let ( let mut is_mouse_down = self.is_mouse_down;
global_time, self.mutate_with_update_context(|avm, context| {
swf_data, if let Some(node) = context.mouse_hovered_object {
swf_version,
background_color,
renderer,
audio,
navigator,
rng,
is_mouse_down,
) = (
self.global_time,
&mut self.swf_data,
self.swf_version,
&mut self.background_color,
&mut self.renderer,
&mut self.audio,
&mut self.navigator,
&mut self.rng,
&mut self.is_mouse_down,
);
self.gc_arena.mutate(|gc_context, gc_root| {
let mut root_data = gc_root.0.write(gc_context);
let mouse_hover_node = root_data.mouse_hover_node;
let (root, library, action_queue, mut avm) = root_data.get();
let mut update_context = UpdateContext {
player_version,
global_time,
swf_data,
swf_version,
library,
background_color,
rng,
renderer,
audio,
navigator,
action_queue,
gc_context,
root,
active_clip: root,
start_clip: root,
target_clip: Some(root),
target_path: Value::Undefined,
system_prototypes: avm.prototypes().clone(),
};
if let Some(node) = mouse_hover_node {
if let Some(button) = node.clone().as_button_mut() { if let Some(button) = node.clone().as_button_mut() {
match event { match event {
PlayerEvent::MouseDown { .. } => { PlayerEvent::MouseDown { .. } => {
*is_mouse_down = true; is_mouse_down = true;
needs_render = true; needs_render = true;
update_context.active_clip = node; context.active_clip = node;
button.handle_button_event(&mut update_context, ButtonEvent::Press); button.handle_button_event(context, ButtonEvent::Press);
} }
PlayerEvent::MouseUp { .. } => { PlayerEvent::MouseUp { .. } => {
*is_mouse_down = false; is_mouse_down = false;
needs_render = true; needs_render = true;
update_context.active_clip = node; context.active_clip = node;
button.handle_button_event(&mut update_context, ButtonEvent::Release); button.handle_button_event(context, ButtonEvent::Release);
} }
_ => (), _ => (),
@ -386,9 +342,9 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
} }
} }
Self::run_actions(&mut avm, &mut update_context); Self::run_actions(avm, context);
}); });
self.is_mouse_down = is_mouse_down;
if needs_render { if needs_render {
// Update display after mouse events. // Update display after mouse events.
self.render(); self.render();
@ -396,73 +352,36 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
} }
fn update_roll_over(&mut self) -> bool { fn update_roll_over(&mut self) -> bool {
let player_version = self.player_version;
// TODO: While the mouse is down, maintain the hovered node. // TODO: While the mouse is down, maintain the hovered node.
if self.is_mouse_down { if self.is_mouse_down {
return false; return false;
} }
let mouse_pos = self.mouse_pos;
let (global_time, swf_data, swf_version, background_color, renderer, audio, navigator, rng) = (
self.global_time,
&mut self.swf_data,
self.swf_version,
&mut self.background_color,
&mut self.renderer,
&mut self.audio,
&mut self.navigator,
&mut self.rng,
);
let mouse_pos = &self.mouse_pos;
// Check hovered object. // Check hovered object.
self.gc_arena.mutate(|gc_context, gc_root| { self.mutate_with_update_context(|avm, context| {
let mut root_data = gc_root.0.write(gc_context); let root = context.root;
let new_hover_node = root_data let new_hovered = root.mouse_pick(root, (mouse_pos.0, mouse_pos.1));
.root let cur_hovered = context.mouse_hovered_object;
.mouse_pick(root_data.root, (mouse_pos.0, mouse_pos.1)); if cur_hovered.map(|d| d.as_ptr()) != new_hovered.map(|d| d.as_ptr()) {
let cur_hover_node = root_data.mouse_hover_node;
let (root, library, action_queue, avm) = root_data.get();
if cur_hover_node.map(|d| d.as_ptr()) != new_hover_node.map(|d| d.as_ptr()) {
let mut update_context = UpdateContext {
player_version,
global_time,
swf_data,
swf_version,
library,
background_color,
rng,
renderer,
audio,
navigator,
action_queue,
gc_context,
active_clip: root,
start_clip: root,
target_clip: Some(root),
root,
target_path: Value::Undefined,
system_prototypes: avm.prototypes().clone(),
};
// RollOut of previous node. // RollOut of previous node.
if let Some(mut node) = cur_hover_node { if let Some(mut node) = cur_hovered {
if let Some(mut button) = node.as_button_mut().copied() { if let Some(mut button) = node.as_button_mut().copied() {
update_context.active_clip = node; context.active_clip = node;
button.handle_button_event(&mut update_context, ButtonEvent::RollOut); button.handle_button_event(context, ButtonEvent::RollOut);
} }
} }
// RollOver on new node. // RollOver on new node.
if let Some(mut node) = new_hover_node { if let Some(mut node) = new_hovered {
if let Some(mut button) = node.as_button_mut().copied() { if let Some(mut button) = node.as_button_mut().copied() {
update_context.active_clip = node; context.active_clip = node;
button.handle_button_event(&mut update_context, ButtonEvent::RollOver); button.handle_button_event(context, ButtonEvent::RollOver);
} }
} }
// TODO cur_hover_node = new_hover_node; context.mouse_hovered_object = new_hovered;
Self::run_actions(avm, &mut update_context); Self::run_actions(avm, context);
true true
} else { } else {
false false
@ -471,61 +390,17 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
} }
fn preload(&mut self) { fn preload(&mut self) {
let ( self.mutate_with_update_context(|_avm, context| {
player_version,
global_time,
swf_data,
swf_version,
background_color,
renderer,
audio,
navigator,
rng,
) = (
self.player_version,
self.global_time,
&mut self.swf_data,
self.swf_version,
&mut self.background_color,
&mut self.renderer,
&mut self.audio,
&mut self.navigator,
&mut self.rng,
);
self.gc_arena.mutate(|gc_context, gc_root| {
let mut root_data = gc_root.0.write(gc_context);
let (mut root, library, action_queue, avm) = root_data.get();
let mut update_context = UpdateContext {
player_version,
global_time,
swf_data,
swf_version,
library,
background_color,
rng,
renderer,
audio,
navigator,
action_queue,
gc_context,
active_clip: root,
start_clip: root,
target_clip: Some(root),
root,
target_path: Value::Undefined,
system_prototypes: avm.prototypes().clone(),
};
let mut morph_shapes = fnv::FnvHashMap::default(); let mut morph_shapes = fnv::FnvHashMap::default();
let mut root = context.root;
root.as_movie_clip_mut() root.as_movie_clip_mut()
.unwrap() .unwrap()
.preload(&mut update_context, &mut morph_shapes); .preload(context, &mut morph_shapes);
// Finalize morph shapes. // Finalize morph shapes.
for (id, static_data) in morph_shapes { for (id, static_data) in morph_shapes {
let morph_shape = MorphShape::new(gc_context, static_data); let morph_shape = MorphShape::new(context.gc_context, static_data);
update_context.library.register_character( context.library.register_character(
id, id,
crate::character::Character::MorphShape(Box::new(morph_shape)), crate::character::Character::MorphShape(Box::new(morph_shape)),
); );
@ -534,55 +409,10 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
} }
pub fn run_frame(&mut self) { pub fn run_frame(&mut self) {
let ( self.mutate_with_update_context(|avm, context| {
player_version, let mut root = context.root;
global_time, root.run_frame(context);
swf_data, Self::run_actions(avm, context);
swf_version,
background_color,
renderer,
audio,
navigator,
rng,
) = (
self.player_version,
self.global_time,
&mut self.swf_data,
self.swf_version,
&mut self.background_color,
&mut self.renderer,
&mut self.audio,
&mut self.navigator,
&mut self.rng,
);
self.gc_arena.mutate(|gc_context, gc_root| {
let mut root_data = gc_root.0.write(gc_context);
let (mut root, library, action_queue, avm) = root_data.get();
let mut update_context = UpdateContext {
player_version,
global_time,
swf_data,
swf_version,
library,
background_color,
rng,
renderer,
audio,
navigator,
action_queue,
gc_context,
active_clip: root,
start_clip: root,
target_clip: Some(root),
root,
target_path: Value::Undefined,
system_prototypes: avm.prototypes().clone(),
};
root.run_frame(&mut update_context);
Self::run_actions(avm, &mut update_context);
}); });
// Update mouse state (check for new hovered button, etc.) // Update mouse state (check for new hovered button, etc.)
@ -714,6 +544,70 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
}; };
} }
/// Runs the closure `f` with an `UpdateContext`.
/// This takes cares of populating the `UpdateContext` struct, avoiding borrow issues.
fn mutate_with_update_context<F, R>(&mut self, f: F) -> R
where
F: for<'a, 'gc> FnOnce(&mut Avm1<'gc>, &mut UpdateContext<'a, 'gc, '_>) -> R,
{
// We have to do this piecewise borrowing of fields before the closure to avoid
// completely borrowing `self`.
let (
player_version,
global_time,
swf_data,
swf_version,
background_color,
renderer,
audio,
navigator,
rng,
) = (
self.player_version,
self.global_time,
&mut self.swf_data,
self.swf_version,
&mut self.background_color,
&mut self.renderer,
&mut self.audio,
&mut self.navigator,
&mut self.rng,
);
self.gc_arena.mutate(|gc_context, gc_root| {
let mut root_data = gc_root.0.write(gc_context);
let mouse_hovered_object = root_data.mouse_hovered_object;
let (root, library, action_queue, avm) = root_data.update_context_params();
let mut update_context = UpdateContext {
player_version,
global_time,
swf_data,
swf_version,
library,
background_color,
rng,
renderer,
audio,
navigator,
action_queue,
gc_context,
active_clip: root,
start_clip: root,
target_clip: Some(root),
root,
target_path: Value::Undefined,
system_prototypes: avm.prototypes().clone(),
mouse_hovered_object,
};
let ret = f(avm, &mut update_context);
// Hovered object may have been updated; copy it back to the GC root.
root_data.mouse_hovered_object = update_context.mouse_hovered_object;
ret
})
}
/// Loads font data from the given buffer. /// Loads font data from the given buffer.
/// The buffer should be the `DefineFont3` info for the tag. /// The buffer should be the `DefineFont3` info for the tag.
/// The tag header should not be included. /// The tag header should not be included.