avm1: Simplify `TDisplayObject::avm1_root()`
Make it infailable.
This commit is contained in:
parent
355bd35935
commit
5a1e417526
|
@ -654,7 +654,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
let depth = self.context.avm1.pop();
|
||||
let target = self.context.avm1.pop();
|
||||
let source = self.context.avm1.pop();
|
||||
let start_clip = self.target_clip_or_root()?;
|
||||
let start_clip = self.target_clip_or_root();
|
||||
let source_clip = self.resolve_target_display_object(start_clip, source, true)?;
|
||||
|
||||
if let Some(movie_clip) = source_clip.and_then(|o| o.as_movie_clip()) {
|
||||
|
@ -722,7 +722,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
fn action_call(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> {
|
||||
// Runs any actions on the given frame.
|
||||
let arg = self.context.avm1.pop();
|
||||
let target = self.target_clip_or_root()?;
|
||||
let target = self.target_clip_or_root();
|
||||
|
||||
// The parameter can be a frame # or a path to a movie clip with a frame number.
|
||||
let mut call_frame = None;
|
||||
|
@ -778,7 +778,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
let variable = self.get_variable(fn_name)?;
|
||||
|
||||
let result = variable.call_with_default_this(
|
||||
self.target_clip_or_root()?.object().coerce_to_object(self),
|
||||
self.target_clip_or_root().object().coerce_to_object(self),
|
||||
fn_name,
|
||||
self,
|
||||
&args,
|
||||
|
@ -1196,11 +1196,11 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
if let Value::Object(target) = target {
|
||||
target.as_display_object()
|
||||
} else {
|
||||
let start = self.target_clip_or_root()?;
|
||||
let start = self.target_clip_or_root();
|
||||
self.resolve_target_display_object(start, target, true)?
|
||||
}
|
||||
} else {
|
||||
Some(self.target_clip_or_root()?)
|
||||
Some(self.target_clip_or_root())
|
||||
};
|
||||
|
||||
if action.is_load_vars() {
|
||||
|
@ -1313,7 +1313,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
fn action_goto_frame_2(&mut self, action: GotoFrame2) -> Result<FrameControl<'gc>, Error<'gc>> {
|
||||
// Version 4+ gotoAndPlay/gotoAndStop
|
||||
// Param can either be a frame number or a frame label.
|
||||
if let Some(clip) = self.target_clip_or_root()?.as_movie_clip() {
|
||||
if let Some(clip) = self.target_clip_or_root().as_movie_clip() {
|
||||
let frame = self.context.avm1.pop();
|
||||
let _ = globals::movie_clip::goto_frame(
|
||||
clip,
|
||||
|
@ -1764,7 +1764,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
|
||||
fn action_remove_sprite(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> {
|
||||
let target = self.context.avm1.pop();
|
||||
let start_clip = self.target_clip_or_root()?;
|
||||
let start_clip = self.target_clip_or_root();
|
||||
let target_clip = self.resolve_target_display_object(start_clip, target, true)?;
|
||||
|
||||
if let Some(target_clip) = target_clip {
|
||||
|
@ -1871,7 +1871,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
};
|
||||
|
||||
let scope = self.scope_cell();
|
||||
let clip_obj = self.target_clip_or_root()?.object().coerce_to_object(self);
|
||||
let clip_obj = self.target_clip_or_root().object().coerce_to_object(self);
|
||||
|
||||
self.set_scope(Scope::new_target_scope(
|
||||
scope,
|
||||
|
@ -1891,7 +1891,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
|
||||
fn action_start_drag(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> {
|
||||
let target = self.context.avm1.pop();
|
||||
let start_clip = self.target_clip_or_root()?;
|
||||
let start_clip = self.target_clip_or_root();
|
||||
let display_object = self.resolve_target_display_object(start_clip, target, true)?;
|
||||
if let Some(display_object) = display_object {
|
||||
let lock_center = self.context.avm1.pop();
|
||||
|
@ -2389,7 +2389,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
return Ok(None);
|
||||
}
|
||||
|
||||
let root = start.avm1_root(&self.context)?;
|
||||
let root = start.avm1_root();
|
||||
let start = start.object().coerce_to_object(self);
|
||||
Ok(self
|
||||
.resolve_target_path(root, start, &path, false)?
|
||||
|
@ -2475,7 +2475,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
if first_element && name == b"this" {
|
||||
self.this_cell()
|
||||
} else if first_element && name == b"_root" {
|
||||
self.root_object()?
|
||||
self.root_object()
|
||||
} else {
|
||||
// Get the value from the object.
|
||||
// Resolves display object instances first, then local variables.
|
||||
|
@ -2525,7 +2525,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
|
||||
let mut current_scope = Some(self.scope_cell());
|
||||
while let Some(scope) = current_scope {
|
||||
let avm1_root = start.avm1_root(&self.context)?;
|
||||
let avm1_root = start.avm1_root();
|
||||
if let Some(object) =
|
||||
self.resolve_target_path(avm1_root, *scope.read().locals(), path, true)?
|
||||
{
|
||||
|
@ -2569,7 +2569,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
/// scope chain.
|
||||
pub fn get_variable(&mut self, path: AvmString<'gc>) -> Result<CallableValue<'gc>, Error<'gc>> {
|
||||
// Resolve a variable path for a GetVariable action.
|
||||
let start = self.target_clip_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.
|
||||
|
@ -2580,7 +2580,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
|
||||
let mut current_scope = Some(self.scope_cell());
|
||||
while let Some(scope) = current_scope {
|
||||
let avm1_root = start.avm1_root(&self.context)?;
|
||||
let avm1_root = start.avm1_root();
|
||||
if let Some(object) =
|
||||
self.resolve_target_path(avm1_root, *scope.read().locals(), path, true)?
|
||||
{
|
||||
|
@ -2599,7 +2599,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
if path.contains(b'/') {
|
||||
let mut current_scope = Some(self.scope_cell());
|
||||
while let Some(scope) = current_scope {
|
||||
let avm1_root = start.avm1_root(&self.context)?;
|
||||
let avm1_root = start.avm1_root();
|
||||
if let Some(object) =
|
||||
self.resolve_target_path(avm1_root, *scope.read().locals(), &path, false)?
|
||||
{
|
||||
|
@ -2642,7 +2642,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
value: Value<'gc>,
|
||||
) -> Result<(), Error<'gc>> {
|
||||
// Resolve a variable path for a GetVariable action.
|
||||
let start = self.target_clip_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() {
|
||||
|
@ -2660,7 +2660,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
|
||||
let mut current_scope = Some(self.scope_cell());
|
||||
while let Some(scope) = current_scope {
|
||||
let avm1_root = start.avm1_root(&self.context)?;
|
||||
let avm1_root = start.avm1_root();
|
||||
if let Some(object) =
|
||||
self.resolve_target_path(avm1_root, *scope.read().locals(), path, true)?
|
||||
{
|
||||
|
@ -2709,17 +2709,14 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
/// Actions that affect `root` after an invalid `tellTarget` will use this.
|
||||
///
|
||||
/// The `root` is determined relative to the base clip that defined the
|
||||
pub fn target_clip_or_root(&self) -> Result<DisplayObject<'gc>, Error<'gc>> {
|
||||
if let Some(target) = self.target_clip() {
|
||||
return Ok(target);
|
||||
}
|
||||
|
||||
self.base_clip().avm1_root(&self.context)
|
||||
pub fn target_clip_or_root(&self) -> DisplayObject<'gc> {
|
||||
self.target_clip()
|
||||
.unwrap_or_else(|| self.base_clip().avm1_root())
|
||||
}
|
||||
|
||||
/// Obtain the value of `_root`.
|
||||
pub fn root_object(&self) -> Result<Value<'gc>, Error<'gc>> {
|
||||
Ok(self.base_clip().avm1_root(&self.context)?.object())
|
||||
pub fn root_object(&self) -> Value<'gc> {
|
||||
self.base_clip().avm1_root().object()
|
||||
}
|
||||
|
||||
/// Returns whether property keys should be case sensitive based on the current SWF version.
|
||||
|
@ -2887,7 +2884,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
fn set_target(&mut self, target: &WStr) -> Result<FrameControl<'gc>, Error<'gc>> {
|
||||
let base_clip = self.base_clip();
|
||||
let new_target_clip;
|
||||
let root = base_clip.avm1_root(&self.context)?;
|
||||
let root = base_clip.avm1_root();
|
||||
let start = base_clip.object().coerce_to_object(self);
|
||||
if target.is_empty() {
|
||||
new_target_clip = Some(base_clip);
|
||||
|
@ -2925,7 +2922,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
|
|||
self.set_target_clip(new_target_clip);
|
||||
|
||||
let scope = self.scope_cell();
|
||||
let clip_obj = self.target_clip_or_root()?.object().coerce_to_object(self);
|
||||
let clip_obj = self.target_clip_or_root().object().coerce_to_object(self);
|
||||
|
||||
self.set_scope(Scope::new_target_scope(
|
||||
scope,
|
||||
|
|
|
@ -18,9 +18,6 @@ pub enum Error<'gc> {
|
|||
#[error("Couldn't parse SWF")]
|
||||
InvalidSwf(#[from] swf::error::Error),
|
||||
|
||||
#[error("Attempted to interact with a rootless display object in AVM1. Such objects can only be created in AS3, this is a runtime bug in Ruffle.")]
|
||||
InvalidDisplayObjectHierarchy,
|
||||
|
||||
#[error("A script has thrown a custom error.")]
|
||||
ThrownValue(Value<'gc>),
|
||||
}
|
||||
|
|
|
@ -350,10 +350,7 @@ impl<'gc> Executable<'gc> {
|
|||
}
|
||||
|
||||
if af.flags.contains(FunctionFlags::PRELOAD_ROOT) {
|
||||
frame.set_local_register(
|
||||
preload_r,
|
||||
af.base_clip.avm1_root(&frame.context)?.object(),
|
||||
);
|
||||
frame.set_local_register(preload_r, af.base_clip.avm1_root().object());
|
||||
preload_r += 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -1149,7 +1149,7 @@ pub fn load_bitmap<'gc>(
|
|||
|
||||
let library = &*activation.context.library;
|
||||
|
||||
let movie = activation.target_clip_or_root()?.movie();
|
||||
let movie = activation.target_clip_or_root().movie();
|
||||
|
||||
let renderer = &mut activation.context.renderer;
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ fn target<'gc>(
|
|||
let target = this.get("target", activation)?;
|
||||
// Undefined or empty target is no-op.
|
||||
if target != Value::Undefined {
|
||||
let start_clip = activation.target_clip_or_root()?;
|
||||
let start_clip = activation.target_clip_or_root();
|
||||
activation.resolve_target_display_object(start_clip, target, false)
|
||||
} else {
|
||||
Ok(None)
|
||||
|
|
|
@ -53,7 +53,7 @@ pub fn get_root<'gc>(
|
|||
_this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error<'gc>> {
|
||||
activation.root_object()
|
||||
Ok(activation.root_object())
|
||||
}
|
||||
|
||||
pub fn get_parent<'gc>(
|
||||
|
|
|
@ -134,7 +134,7 @@ pub fn hit_test<'gc>(
|
|||
// 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 = movie_clip
|
||||
.avm1_root(&activation.context)?
|
||||
.avm1_root()
|
||||
.local_to_global((Twips::from_pixels(x), Twips::from_pixels(y)));
|
||||
let ret = if shape {
|
||||
movie_clip.hit_test_shape(
|
||||
|
|
|
@ -46,7 +46,7 @@ fn load_clip<'gc>(
|
|||
if let Value::String(url) = url {
|
||||
let target = match target {
|
||||
Value::String(_) => {
|
||||
let start_clip = activation.target_clip_or_root()?;
|
||||
let start_clip = activation.target_clip_or_root();
|
||||
activation.resolve_target_display_object(start_clip, *target, true)?
|
||||
}
|
||||
Value::Number(level_id) => {
|
||||
|
@ -90,7 +90,7 @@ fn unload_clip<'gc>(
|
|||
if let [target, ..] = args {
|
||||
let target = match target {
|
||||
Value::String(_) => {
|
||||
let start_clip = activation.target_clip_or_root()?;
|
||||
let start_clip = activation.target_clip_or_root();
|
||||
activation.resolve_target_display_object(start_clip, *target, true)?
|
||||
}
|
||||
Value::Number(level_id) => {
|
||||
|
@ -123,7 +123,7 @@ fn get_progress<'gc>(
|
|||
if let [target, ..] = args {
|
||||
let target = match target {
|
||||
Value::String(_) => {
|
||||
let start_clip = activation.target_clip_or_root()?;
|
||||
let start_clip = activation.target_clip_or_root();
|
||||
activation.resolve_target_display_object(start_clip, *target, true)?
|
||||
}
|
||||
Value::Number(level_id) => {
|
||||
|
|
|
@ -39,7 +39,7 @@ pub fn constructor<'gc>(
|
|||
// 1st parameter is the movie clip that "owns" all sounds started by this object.
|
||||
// `Sound.setTransform`, `Sound.stop`, etc. will affect all sounds owned by this clip.
|
||||
let owner = if let Some(owner) = args.get(0) {
|
||||
let start_clip = activation.target_clip_or_root()?;
|
||||
let start_clip = activation.target_clip_or_root();
|
||||
activation.resolve_target_display_object(start_clip, *owner, false)?
|
||||
} else {
|
||||
None
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use crate::avm1::{
|
||||
Error as Avm1Error, Object as Avm1Object, TObject as Avm1TObject, Value as Avm1Value,
|
||||
};
|
||||
use crate::avm1::{Object as Avm1Object, TObject as Avm1TObject, Value as Avm1Value};
|
||||
use crate::avm2::{
|
||||
Activation as Avm2Activation, Avm2, Error as Avm2Error, Event as Avm2Event,
|
||||
EventData as Avm2EventData, Namespace as Avm2Namespace, Object as Avm2Object,
|
||||
|
@ -1314,55 +1312,22 @@ pub trait TDisplayObject<'gc>:
|
|||
true
|
||||
}
|
||||
|
||||
/// Obtain the top-most non-Stage parent of the display tree hierarchy, if
|
||||
/// a suitable object exists. If none such object exists, this function
|
||||
/// yields an AVM1 error (which shouldn't happen in normal usage).
|
||||
/// Obtain the top-most non-Stage parent of the display tree hierarchy.
|
||||
///
|
||||
/// This function implements the AVM1 concept of root clips. For the AVM2
|
||||
/// version, see `avm2_root`.
|
||||
fn avm1_root(
|
||||
&self,
|
||||
context: &UpdateContext<'_, 'gc, '_>,
|
||||
) -> Result<DisplayObject<'gc>, Avm1Error<'gc>> {
|
||||
let mut parent = if self.lock_root() {
|
||||
None
|
||||
} else {
|
||||
self.avm1_parent()
|
||||
};
|
||||
|
||||
while let Some(p) = parent {
|
||||
if p.lock_root() {
|
||||
fn avm1_root(&self) -> DisplayObject<'gc> {
|
||||
let mut root = (*self).into();
|
||||
loop {
|
||||
if root.lock_root() {
|
||||
break;
|
||||
}
|
||||
|
||||
let grandparent = p.avm1_parent();
|
||||
|
||||
if grandparent.is_none() {
|
||||
break;
|
||||
}
|
||||
|
||||
parent = grandparent;
|
||||
root = match root.avm1_parent() {
|
||||
Some(parent) => parent,
|
||||
None => break,
|
||||
};
|
||||
}
|
||||
|
||||
parent
|
||||
.ok_or(Avm1Error::InvalidDisplayObjectHierarchy)
|
||||
.or_else(|_| {
|
||||
if let Avm1Value::Object(object) = self.object() {
|
||||
object
|
||||
.as_display_object()
|
||||
.ok_or(Avm1Error::InvalidDisplayObjectHierarchy)
|
||||
} else if let Avm2Value::Object(object) = self.object2() {
|
||||
if self.is_on_stage(context) {
|
||||
object
|
||||
.as_display_object()
|
||||
.ok_or(Avm1Error::InvalidDisplayObjectHierarchy)
|
||||
} else {
|
||||
Err(Avm1Error::InvalidDisplayObjectHierarchy)
|
||||
}
|
||||
} else {
|
||||
Err(Avm1Error::InvalidDisplayObjectHierarchy)
|
||||
}
|
||||
})
|
||||
root
|
||||
}
|
||||
|
||||
/// Obtain the top-most non-Stage parent of the display tree hierarchy, if
|
||||
|
|
Loading…
Reference in New Issue