Abolish `context.root` completely.

`_root` is calculated dynamically based on the clip the currently executing function was called in.

Other things that used `context.root` have been changed to either update all layers or just update layer 0, which is the former `context.root`.
This commit is contained in:
David Wendt 2020-01-29 23:21:06 -05:00
parent 88b10f21c4
commit aa6aba13dd
13 changed files with 82 additions and 74 deletions

View File

@ -149,15 +149,14 @@ impl<'gc> Avm1<'gc> {
/// The current target clip of the executing code, or `root` if there is none.
/// Actions that affect `root` after an invalid `tellTarget` will use this.
pub fn target_clip_or_root(
&self,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> DisplayObject<'gc> {
///
/// The `root` is determined relative to the base clip that defined the
pub fn target_clip_or_root(&self) -> DisplayObject<'gc> {
self.current_stack_frame()
.unwrap()
.read()
.target_clip()
.unwrap_or(context.root)
.unwrap_or_else(|| self.base_clip().root())
}
/// Convert the current locals pool into a set of form values.
@ -695,7 +694,7 @@ impl<'gc> Avm1<'gc> {
start: DisplayObject<'gc>,
path: &str,
) -> Result<Option<Object<'gc>>, Error> {
let root = context.root;
let root = start.root();
// Empty path resolves immediately to start clip.
if path.is_empty() {
@ -809,8 +808,7 @@ impl<'gc> Avm1<'gc> {
path: &'s str,
) -> Result<ReturnValue<'gc>, Error> {
// Resolve a variable path for a GetVariable action.
let root = context.root;
let start = self.target_clip().unwrap_or(root);
let start = self.target_clip_or_root();
// Find the right-most : or . in the path.
// If we have one, we must resolve as a target path.
@ -878,8 +876,7 @@ impl<'gc> Avm1<'gc> {
value: Value<'gc>,
) -> Result<(), Error> {
// Resolve a variable path for a GetVariable action.
let root = context.root;
let start = self.target_clip().unwrap_or(root);
let start = self.target_clip_or_root();
// If the target clip is invalid, we default to root for the variable path.
if path.is_empty() {
@ -1092,7 +1089,7 @@ impl<'gc> Avm1<'gc> {
let depth = self.pop();
let target = self.pop();
let source = self.pop();
let start_clip = self.target_clip_or_root(context);
let start_clip = self.target_clip_or_root();
let source_clip = self.resolve_target_display_object(context, start_clip, source)?;
if let Some(movie_clip) = source_clip.and_then(|o| o.as_movie_clip()) {
@ -1161,7 +1158,7 @@ impl<'gc> Avm1<'gc> {
fn action_call(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
// Runs any actions on the given frame.
let frame = self.pop();
let clip = self.target_clip_or_root(context);
let clip = self.target_clip_or_root();
if let Some(clip) = clip.as_movie_clip() {
// Use frame # if parameter is a number, otherwise cast to string and check for frame labels.
let frame = if let Ok(frame) = frame.as_u32() {
@ -1180,7 +1177,7 @@ impl<'gc> Avm1<'gc> {
// so we want to push the stack frames in reverse order.
for action in clip.actions_on_frame(context, frame).rev() {
self.insert_stack_frame_for_action(
self.target_clip_or_root(context),
self.target_clip_or_root(),
self.current_swf_version(),
action,
context,
@ -1214,7 +1211,7 @@ impl<'gc> Avm1<'gc> {
.read()
.resolve(fn_name.as_string()?, self, context)?
.resolve(self, context)?;
let this = self.target_clip_or_root(context).object().as_object()?;
let this = self.target_clip_or_root().object().as_object()?;
target_fn.call(self, context, this, &args)?.push(self);
Ok(())
@ -1235,7 +1232,7 @@ impl<'gc> Avm1<'gc> {
match method_name {
Value::Undefined | Value::Null => {
let this = self.target_clip_or_root(context).object();
let this = self.target_clip_or_root().object();
if let Ok(this) = this.as_object() {
object.call(self, context, this, &args)?.push(self);
} else {
@ -1340,7 +1337,7 @@ impl<'gc> Avm1<'gc> {
params,
scope,
constant_pool,
self.target_clip_or_root(context),
self.target_clip_or_root(),
);
let prototype =
ScriptObject::object(context.gc_context, Some(self.prototypes.object)).into();
@ -1606,8 +1603,8 @@ impl<'gc> Avm1<'gc> {
}
/// Obtain the value of `_root`.
pub fn root_object(&self, context: &mut UpdateContext<'_, 'gc, '_>) -> Value<'gc> {
context.root.object()
pub fn root_object(&self, _context: &mut UpdateContext<'_, 'gc, '_>) -> Value<'gc> {
self.base_clip().root().object()
}
/// Obtain the value of `_global`.
@ -1692,11 +1689,11 @@ impl<'gc> Avm1<'gc> {
if let Value::Object(target) = target {
target.as_display_object()
} else {
let start = self.target_clip_or_root(context);
let start = self.target_clip_or_root();
self.resolve_target_display_object(context, start, target.clone())?
}
} else {
Some(self.target_clip_or_root(context))
Some(self.target_clip_or_root())
};
if is_load_vars {
@ -2209,7 +2206,7 @@ impl<'gc> Avm1<'gc> {
context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<(), Error> {
let target = self.pop();
let start_clip = self.target_clip_or_root(context);
let start_clip = self.target_clip_or_root();
let target_clip = self.resolve_target_display_object(context, start_clip, target)?;
if let Some(target_clip) = target_clip.and_then(|o| o.as_movie_clip()) {
@ -2295,16 +2292,15 @@ impl<'gc> Avm1<'gc> {
context: &mut UpdateContext<'_, 'gc, '_>,
target: &str,
) -> Result<(), Error> {
let stack_frame = self.current_stack_frame().unwrap();
let mut sf = stack_frame.write(context.gc_context);
let base_clip = sf.base_clip();
let base_clip = self.base_clip();
let new_target_clip;
if target.is_empty() {
sf.set_target_clip(Some(base_clip));
new_target_clip = Some(base_clip);
} else if let Some(clip) = self
.resolve_target_path(context, base_clip, target)?
.and_then(|o| o.as_display_object())
{
sf.set_target_clip(Some(clip));
new_target_clip = Some(clip);
} else {
log::warn!("SetTarget failed: {} not found", target);
// TODO: Emulate AVM1 trace error message.
@ -2313,13 +2309,17 @@ impl<'gc> Avm1<'gc> {
// When SetTarget has an invalid target, subsequent GetVariables act
// as if they are targeting root, but subsequent Play/Stop/etc.
// fail silenty.
sf.set_target_clip(None);
new_target_clip = None;
}
let stack_frame = self.current_stack_frame().unwrap();
let mut sf = stack_frame.write(context.gc_context);
sf.set_target_clip(new_target_clip);
let scope = sf.scope_cell();
let clip_obj = sf
.target_clip()
.unwrap_or(context.root)
.unwrap_or_else(|| sf.base_clip().root())
.object()
.as_object()
.unwrap();
@ -2367,7 +2367,7 @@ impl<'gc> Avm1<'gc> {
let scope = sf.scope_cell();
let clip_obj = sf
.target_clip()
.unwrap_or(context.root)
.unwrap_or_else(|| sf.base_clip().root())
.object()
.as_object()
.unwrap();
@ -2385,7 +2385,7 @@ impl<'gc> Avm1<'gc> {
fn action_start_drag(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
let target = self.pop();
let start_clip = self.target_clip_or_root(context);
let start_clip = self.target_clip_or_root();
let display_object = self.resolve_target_display_object(context, start_clip, target)?;
if let Some(display_object) = display_object {
let lock_center = self.pop();

View File

@ -82,7 +82,7 @@ fn target<'gc>(
// This means calls on the same `Color` object could set the color of different clips
// depending on which timeline its called from!
let target = this.get("target", avm, context)?.resolve(avm, context)?;
let start_clip = avm.target_clip_or_root(context);
let start_clip = avm.target_clip_or_root();
avm.resolve_target_display_object(context, start_clip, target)
}

View File

@ -102,8 +102,8 @@ pub fn hit_test<'gc>(
if x.is_finite() && y.is_finite() {
// The docs say the point is in "Stage coordinates", but actually they are in root coordinates.
// root can be moved via _root._x etc., so we actually have to transform from root to world space.
let point = context
.root
let point = movie_clip
.root()
.local_to_global((Twips::from_pixels(x), Twips::from_pixels(y)));
return Ok(movie_clip.hit_test(point).into());
}

View File

@ -800,7 +800,7 @@ pub fn xml_load<'gc>(
this.set("loaded", false.into(), avm, ac)?;
let fetch = ac.navigator.fetch(url, RequestOptions::get());
let target_clip = avm.target_clip_or_root(ac);
let target_clip = avm.target_clip_or_root();
let process = ac.load_manager.load_xml_into_node(
ac.player.clone().unwrap(),
node,

View File

@ -597,7 +597,6 @@ mod tests {
player_version: 32,
swf: &swf,
layers: &mut layers,
root,
rng: &mut SmallRng::from_seed([0u8; 16]),
action_queue: &mut crate::context::ActionQueue::new(),
audio: &mut NullAudioBackend::new(),

View File

@ -36,7 +36,6 @@ where
player_version: 32,
swf: &swf,
layers: &mut layers,
root,
rng: &mut SmallRng::from_seed([0u8; 16]),
audio: &mut NullAudioBackend::new(),
input: &mut NullInputBackend::new(),

View File

@ -10,7 +10,7 @@ fn locals_into_form_values() {
19,
avm.global_object_cell(),
context.gc_context,
context.root,
*context.layers.get(&0).expect("root layer in test"),
);
let my_locals = my_activation.scope().locals().to_owned();

View File

@ -68,10 +68,6 @@ pub struct UpdateContext<'a, 'gc, 'gc_context> {
/// All loaded layers of the current player.
pub layers: &'a mut BTreeMap<u32, DisplayObject<'gc>>,
/// The root of the current timeline being updated.
/// This will always be one of the layers in `layers`.
pub root: DisplayObject<'gc>,
/// The current set of system-specified prototypes to use when constructing
/// new built-in objects.
pub system_prototypes: avm1::SystemPrototypes<'gc>,
@ -106,9 +102,6 @@ pub struct QueuedActions<'gc> {
/// The movie clip this ActionScript is running on.
pub clip: DisplayObject<'gc>,
/// The root timeline this action was queued in.
pub root: DisplayObject<'gc>,
/// The type of action this is, along with the corresponding bytecode/method data.
pub action_type: ActionType<'gc>,
@ -145,13 +138,11 @@ impl<'gc> ActionQueue<'gc> {
pub fn queue_actions(
&mut self,
clip: DisplayObject<'gc>,
root: DisplayObject<'gc>,
action_type: ActionType<'gc>,
is_unload: bool,
) {
self.queue.push_back(QueuedActions {
clip,
root,
action_type,
is_unload,
})

View File

@ -1,4 +1,4 @@
use crate::avm1::{Object, Value};
use crate::avm1::{Object, TObject, Value};
use crate::context::{RenderContext, UpdateContext};
use crate::player::NEWEST_PLAYER_VERSION;
use crate::prelude::*;
@ -366,7 +366,7 @@ impl<'gc> DisplayObjectBase<'gc> {
Text(Text<'gc>),
}
)]
pub trait TDisplayObject<'gc>: 'gc + Collect + Debug {
pub trait TDisplayObject<'gc>: 'gc + Collect + Debug + Into<DisplayObject<'gc>> {
fn id(&self) -> CharacterId;
fn depth(&self) -> Depth;
fn set_depth(&self, gc_context: MutationContext<'gc, '_>, depth: Depth);
@ -804,6 +804,34 @@ pub trait TDisplayObject<'gc>: 'gc + Collect + Debug {
fn allow_as_mask(&self) -> bool {
true
}
/// Obtain the top-most parent of the display tree hierarchy.
///
/// This function can panic in the rare case that a top-level display
/// object has not been post-instantiated, or that a top-level display
/// object does not implement `object`.
fn root(&self) -> DisplayObject<'gc> {
let mut parent = self.parent();
while let Some(p) = parent {
let grandparent = p.parent();
if grandparent.is_none() {
break;
}
parent = grandparent;
}
parent
.or_else(|| {
self.object()
.as_object()
.ok()
.and_then(|o| o.as_display_object())
})
.expect("All objects must have root")
}
}
pub enum DisplayObjectPtr {}

View File

@ -370,7 +370,6 @@ impl<'gc> ButtonData<'gc> {
handled = ButtonEventResult::Handled;
context.action_queue.queue_actions(
parent,
context.root,
ActionType::Normal {
bytecode: action.action_data.clone(),
},

View File

@ -923,7 +923,6 @@ impl<'gc> MovieClipData<'gc> {
{
context.action_queue.queue_actions(
self_display_object,
context.root,
ActionType::Normal {
bytecode: clip_action.action_data.clone(),
},
@ -958,7 +957,6 @@ impl<'gc> MovieClipData<'gc> {
if let Some(name) = name {
context.action_queue.queue_actions(
self_display_object,
context.root,
ActionType::Method {
object: self.object.unwrap(),
name,
@ -991,7 +989,6 @@ impl<'gc> MovieClipData<'gc> {
context.load_manager.movie_clip_on_load(
self_display_object,
self.object,
context.root,
context.action_queue,
);
}
@ -1772,7 +1769,6 @@ impl<'gc, 'a> MovieClipData<'gc> {
})?;
context.action_queue.queue_actions(
self_display_object,
context.root,
ActionType::Normal { bytecode: slice },
false,
);
@ -1806,7 +1802,6 @@ impl<'gc, 'a> MovieClipData<'gc> {
})?;
context.action_queue.queue_actions(
self_display_object,
context.root,
ActionType::Init { bytecode: slice },
true,
);

View File

@ -89,13 +89,12 @@ impl<'gc> LoadManager<'gc> {
&mut self,
loaded_clip: DisplayObject<'gc>,
clip_object: Option<Object<'gc>>,
root: DisplayObject<'gc>,
queue: &mut ActionQueue<'gc>,
) {
let mut invalidated_loaders = vec![];
for (index, loader) in self.0.iter_mut() {
if loader.movie_clip_loaded(loaded_clip, clip_object, root, queue) {
if loader.movie_clip_loaded(loaded_clip, clip_object, queue) {
invalidated_loaders.push(index);
}
}
@ -433,7 +432,6 @@ impl<'gc> Loader<'gc> {
&mut self,
loaded_clip: DisplayObject<'gc>,
clip_object: Option<Object<'gc>>,
root: DisplayObject<'gc>,
queue: &mut ActionQueue<'gc>,
) -> bool {
let (clip, broadcaster) = match self {
@ -449,7 +447,6 @@ impl<'gc> Loader<'gc> {
if let Some(broadcaster) = broadcaster {
queue.queue_actions(
clip,
root,
ActionType::Method {
object: broadcaster,
name: "broadcastMessage",

View File

@ -6,7 +6,7 @@ use crate::backend::{
};
use crate::context::{ActionQueue, ActionType, RenderContext, UpdateContext};
use crate::display_object::{MorphShape, MovieClip};
use crate::events::{ButtonEvent, ButtonKeyCode, ClipEvent, PlayerEvent};
use crate::events::{ButtonEvent, ButtonEventResult, ButtonKeyCode, ClipEvent, PlayerEvent};
use crate::library::Library;
use crate::loader::LoadManager;
use crate::prelude::*;
@ -369,9 +369,14 @@ impl Player {
if button_event.is_some() {
self.mutate_with_update_context(|_avm, context| {
let root = context.root;
if let Some(button_event) = button_event {
root.propagate_button_event(context, button_event);
let layers: Vec<DisplayObject<'_>> = context.layers.values().copied().collect();
for layer in layers {
if let Some(button_event) = button_event {
let state = layer.propagate_button_event(context, button_event);
if state == ButtonEventResult::Handled {
return;
}
}
}
});
}
@ -387,16 +392,17 @@ impl Player {
if clip_event.is_some() || mouse_event_name.is_some() {
self.mutate_with_update_context(|_avm, context| {
let root = context.root;
let layers: Vec<DisplayObject<'_>> = context.layers.values().copied().collect();
if let Some(clip_event) = clip_event {
root.propagate_clip_event(context, clip_event);
for layer in layers {
if let Some(clip_event) = clip_event {
layer.propagate_clip_event(context, clip_event);
}
}
if let Some(mouse_event_name) = mouse_event_name {
context.action_queue.queue_actions(
root,
root,
*context.layers.get(&0).expect("root layer"),
ActionType::NotifyListeners {
listener: SystemListener::Mouse,
method: mouse_event_name,
@ -521,7 +527,7 @@ impl Player {
fn preload(&mut self) {
self.mutate_with_update_context(|_avm, context| {
let mut morph_shapes = fnv::FnvHashMap::default();
let root = context.root;
let root = *context.layers.get(&0).expect("root layer");
root.as_movie_clip()
.unwrap()
.preload(context, &mut morph_shapes);
@ -550,8 +556,6 @@ impl Player {
}
for mut layer in layers {
update_context.root = layer;
layer.run_frame(update_context);
}
})
@ -636,8 +640,6 @@ impl Player {
continue;
}
context.root = actions.root;
match actions.action_type {
// DoAction/clip event code
ActionType::Normal { bytecode } => {
@ -772,7 +774,6 @@ impl Player {
let mouse_hovered_object = root_data.mouse_hovered_object;
let (layers, library, action_queue, avm, drag_object, load_manager) =
root_data.update_context_params();
let layer0 = layers.get(&0).expect("Layer 0 should always exist");
let mut update_context = UpdateContext {
player_version,
@ -787,7 +788,6 @@ impl Player {
input,
action_queue,
gc_context,
root: *layer0,
layers,
mouse_hovered_object,
mouse_position,