Replace `Option<Value<'gc>>` with a dedicated `ReturnValue<'gc>` type with associated methods.
This type explicitly signals if an immediate value is to be returned, if a value is to be returned on the stack, or if no return value is to be generated. Holders of a `ReturnValue` can also use `and_then` to schedule a `StackContinuation` to be executed when and if that value is ready. `StackContinuations` now yield `ReturnValues` as well, so they have a moderate level of composability. For example, if you need to get a property from an object and push it on the stack, you can return the result of calling `get` directly and the machinery ensures it eventually gets there.
This commit is contained in:
parent
2a3d324a33
commit
a2ee7f9e3a
199
core/src/avm1.rs
199
core/src/avm1.rs
|
@ -1,6 +1,7 @@
|
||||||
use crate::avm1::function::Avm1Function;
|
use crate::avm1::function::Avm1Function;
|
||||||
use crate::avm1::globals::create_globals;
|
use crate::avm1::globals::create_globals;
|
||||||
use crate::avm1::object::Object;
|
use crate::avm1::object::Object;
|
||||||
|
use crate::avm1::return_value::ReturnValue::*;
|
||||||
use crate::backend::navigator::NavigationMethod;
|
use crate::backend::navigator::NavigationMethod;
|
||||||
use crate::context::UpdateContext;
|
use crate::context::UpdateContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
@ -14,12 +15,15 @@ use swf::avm1::types::{Action, Function};
|
||||||
|
|
||||||
use crate::tag_utils::SwfSlice;
|
use crate::tag_utils::SwfSlice;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod stack_continuation;
|
||||||
mod activation;
|
mod activation;
|
||||||
mod fscommand;
|
mod fscommand;
|
||||||
mod function;
|
mod function;
|
||||||
mod globals;
|
mod globals;
|
||||||
pub mod movie_clip;
|
pub mod movie_clip;
|
||||||
pub mod object;
|
pub mod object;
|
||||||
|
mod return_value;
|
||||||
mod scope;
|
mod scope;
|
||||||
mod value;
|
mod value;
|
||||||
|
|
||||||
|
@ -29,9 +33,6 @@ mod test_utils;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
mod stack_continuation;
|
|
||||||
|
|
||||||
use activation::Activation;
|
use activation::Activation;
|
||||||
use scope::Scope;
|
use scope::Scope;
|
||||||
pub use value::Value;
|
pub use value::Value;
|
||||||
|
@ -111,7 +112,9 @@ impl<'gc> Avm1<'gc> {
|
||||||
|
|
||||||
for k in keys {
|
for k in keys {
|
||||||
let v = locals.read().get(&k, self, context, locals);
|
let v = locals.read().get(&k, self, context, locals);
|
||||||
if let Some(instant_value) = v {
|
|
||||||
|
//TODO: Support on-stack properties
|
||||||
|
if let Immediate(instant_value) = v {
|
||||||
form_values.insert(k, instant_value.clone().into_string());
|
form_values.insert(k, instant_value.clone().into_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,13 +180,8 @@ impl<'gc> Avm1<'gc> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a stack frame for any arbitrary code.
|
/// Add a stack frame for any arbitrary code.
|
||||||
pub fn insert_stack_frame(
|
pub fn insert_stack_frame(&mut self, frame: GcCell<'gc, Activation<'gc>>) {
|
||||||
&mut self,
|
self.stack_frames.push(frame);
|
||||||
frame: Activation<'gc>,
|
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
||||||
) {
|
|
||||||
self.stack_frames
|
|
||||||
.push(GcCell::allocate(context.gc_context, frame));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve the current AVM execution frame.
|
/// Retrieve the current AVM execution frame.
|
||||||
|
@ -275,12 +273,14 @@ impl<'gc> Avm1<'gc> {
|
||||||
let can_return = !self.stack_frames.is_empty();
|
let can_return = !self.stack_frames.is_empty();
|
||||||
|
|
||||||
if let Some(func) = frame.write(context.gc_context).get_then_func() {
|
if let Some(func) = frame.write(context.gc_context).get_then_func() {
|
||||||
let is_continuing = func.returned(self, context, return_value)?;
|
match func.returned(self, context, return_value)? {
|
||||||
if is_continuing {
|
Immediate(val) => self.stack.push(val),
|
||||||
if let Some(fr) = self.current_stack_frame() {
|
ResultOf(new_frame) => {
|
||||||
fr.write(context.gc_context)
|
new_frame
|
||||||
|
.write(context.gc_context)
|
||||||
.and_again(frame, context.gc_context);
|
.and_again(frame, context.gc_context);
|
||||||
}
|
}
|
||||||
|
NoResult => {}
|
||||||
}
|
}
|
||||||
} else if can_return {
|
} else if can_return {
|
||||||
self.stack.push(return_value);
|
self.stack.push(return_value);
|
||||||
|
@ -694,29 +694,17 @@ impl<'gc> Avm1<'gc> {
|
||||||
);
|
);
|
||||||
let this = context.active_clip.read().object().as_object()?.to_owned();
|
let this = context.active_clip.read().object().as_object()?.to_owned();
|
||||||
|
|
||||||
if let Some(target_fn) = target_fn {
|
target_fn
|
||||||
let return_value = target_fn.call(self, context, this, &args)?;
|
.and_then(
|
||||||
if let Some(instant_return) = return_value {
|
self,
|
||||||
self.push(instant_return);
|
context,
|
||||||
}
|
stack_continuation!(
|
||||||
} else {
|
|
||||||
self.stack_frames
|
|
||||||
.last()
|
|
||||||
.unwrap()
|
|
||||||
.write(context.gc_context)
|
|
||||||
.and_then(stack_continuation!(
|
|
||||||
this: GcCell<'gc, Object<'gc>>,
|
this: GcCell<'gc, Object<'gc>>,
|
||||||
args: Vec<Value<'gc>>,
|
args: Vec<Value<'gc>>,
|
||||||
|avm, context, target_fn| {
|
|avm, context, target_fn| { target_fn.call(avm, context, *this, &args) }
|
||||||
let return_value = target_fn.call(avm, context, *this, &args)?;
|
),
|
||||||
if let Some(instant_return) = return_value {
|
)?
|
||||||
avm.push(instant_return);
|
.push(self);
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -736,47 +724,26 @@ impl<'gc> Avm1<'gc> {
|
||||||
match method_name {
|
match method_name {
|
||||||
Value::Undefined | Value::Null => {
|
Value::Undefined | Value::Null => {
|
||||||
let this = context.active_clip.read().object().as_object()?.to_owned();
|
let this = context.active_clip.read().object().as_object()?.to_owned();
|
||||||
let return_value = object.call(self, context, this, &args)?;
|
object.call(self, context, this, &args)?.push(self);
|
||||||
if let Some(instant_return) = return_value {
|
|
||||||
self.push(instant_return);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Value::String(name) => {
|
Value::String(name) => {
|
||||||
if name.is_empty() {
|
if name.is_empty() {
|
||||||
let return_value =
|
object
|
||||||
object.call(self, context, object.as_object()?.to_owned(), &args)?;
|
.call(self, context, object.as_object()?.to_owned(), &args)?
|
||||||
if let Some(instant_return) = return_value {
|
.push(self);
|
||||||
self.push(instant_return);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let callable = object.as_object()?.read().get(
|
let target = object.as_object()?.to_owned();
|
||||||
&name,
|
object
|
||||||
self,
|
.as_object()?
|
||||||
context,
|
.read()
|
||||||
object.as_object()?.to_owned(),
|
.get(&name, self, context, target)
|
||||||
);
|
.and_then(
|
||||||
|
|
||||||
if let Some(Value::Undefined) = callable {
|
|
||||||
return Err(format!("Object method {} is not defined", name).into());
|
|
||||||
} else if let Some(instant_fn) = callable {
|
|
||||||
let return_value = instant_fn.call(
|
|
||||||
self,
|
self,
|
||||||
context,
|
context,
|
||||||
object.as_object()?.to_owned(),
|
stack_continuation!(
|
||||||
&args,
|
target: GcCell<'gc, Object<'gc>>,
|
||||||
)?;
|
|
||||||
if let Some(instant_return) = return_value {
|
|
||||||
self.push(instant_return);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.stack_frames
|
|
||||||
.last()
|
|
||||||
.unwrap()
|
|
||||||
.write(context.gc_context)
|
|
||||||
.and_then(stack_continuation!(
|
|
||||||
args: Vec<Value<'gc>>,
|
|
||||||
name: String,
|
name: String,
|
||||||
object: Value<'gc>,
|
args: Vec<Value<'gc>>,
|
||||||
|avm, context, callable| {
|
|avm, context, callable| {
|
||||||
if let Value::Undefined = callable {
|
if let Value::Undefined = callable {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
|
@ -786,19 +753,11 @@ impl<'gc> Avm1<'gc> {
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let return_value = callable.call(
|
callable.call(avm, context, *target, &args)
|
||||||
avm,
|
|
||||||
context,
|
|
||||||
object.as_object()?.to_owned(),
|
|
||||||
&args,
|
|
||||||
)?;
|
|
||||||
if let Some(instant_return) = return_value {
|
|
||||||
avm.push(instant_return)
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
));
|
),
|
||||||
}
|
)?
|
||||||
|
.push(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -972,48 +931,30 @@ impl<'gc> Avm1<'gc> {
|
||||||
let name_value = self.pop()?;
|
let name_value = self.pop()?;
|
||||||
let name = name_value.as_string()?;
|
let name = name_value.as_string()?;
|
||||||
self.push(Value::Null); // Sentinel that indicates end of enumeration
|
self.push(Value::Null); // Sentinel that indicates end of enumeration
|
||||||
match self
|
self.current_stack_frame()
|
||||||
.current_stack_frame()
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.read()
|
.read()
|
||||||
.resolve(name, self, context)
|
.resolve(name, self, context)
|
||||||
{
|
.and_then(
|
||||||
Some(Value::Object(ob)) => {
|
self,
|
||||||
for k in ob.read().get_keys() {
|
context,
|
||||||
self.push(Value::String(k));
|
stack_continuation!(name_value: Value<'gc>, |avm, _context, object| {
|
||||||
}
|
match object {
|
||||||
}
|
Value::Object(ob) => {
|
||||||
Some(_) => {
|
for k in ob.read().get_keys() {
|
||||||
log::error!("Cannot enumerate properties of {}", name);
|
avm.push(Value::String(k));
|
||||||
|
|
||||||
return Ok(()); //TODO: This is NOT OK(()).
|
|
||||||
}
|
|
||||||
None => self
|
|
||||||
.stack_frames
|
|
||||||
.last()
|
|
||||||
.unwrap()
|
|
||||||
.write(context.gc_context)
|
|
||||||
.and_then(stack_continuation!(
|
|
||||||
name_value: Value<'gc>,
|
|
||||||
|avm, _ctxt, return_value| {
|
|
||||||
match return_value {
|
|
||||||
Value::Object(ob) => {
|
|
||||||
for k in ob.read().get_keys() {
|
|
||||||
avm.push(Value::String(k));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
log::error!(
|
|
||||||
"Cannot enumerate properties of {}",
|
|
||||||
name_value.as_string()?
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_ => log::error!(
|
||||||
|
"Cannot enumerate properties of {}",
|
||||||
|
name_value.as_string()?
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(ReturnValue::NoResult)
|
||||||
}
|
}),
|
||||||
)),
|
)?
|
||||||
};
|
.ignore();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1070,10 +1011,7 @@ impl<'gc> Avm1<'gc> {
|
||||||
let name = name_val.into_string();
|
let name = name_val.into_string();
|
||||||
let object = self.pop()?.as_object()?;
|
let object = self.pop()?.as_object()?;
|
||||||
let this = self.current_stack_frame().unwrap().read().this_cell();
|
let this = self.current_stack_frame().unwrap().read().this_cell();
|
||||||
let value = object.read().get(&name, self, context, this);
|
object.read().get(&name, self, context, this).push(self);
|
||||||
if let Some(value) = value {
|
|
||||||
self.push(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1160,10 +1098,10 @@ impl<'gc> Avm1<'gc> {
|
||||||
if let Some(clip) = node.read().as_movie_clip() {
|
if let Some(clip) = node.read().as_movie_clip() {
|
||||||
let object = clip.object().as_object()?;
|
let object = clip.object().as_object()?;
|
||||||
if object.read().has_property(var_name) {
|
if object.read().has_property(var_name) {
|
||||||
let result = object.read().get(var_name, self, context, object);
|
object
|
||||||
if let Some(value) = result {
|
.read()
|
||||||
self.push(value);
|
.get(var_name, self, context, object)
|
||||||
} //NOTE: Natural return is sufficient for custom get
|
.push(self);
|
||||||
} else {
|
} else {
|
||||||
self.push(Value::Undefined);
|
self.push(Value::Undefined);
|
||||||
}
|
}
|
||||||
|
@ -1172,14 +1110,11 @@ impl<'gc> Avm1<'gc> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if self.current_stack_frame().unwrap().read().is_defined(path) {
|
} else if self.current_stack_frame().unwrap().read().is_defined(path) {
|
||||||
let result = self
|
self.current_stack_frame()
|
||||||
.current_stack_frame()
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.read()
|
.read()
|
||||||
.resolve(path, self, context);
|
.resolve(path, self, context)
|
||||||
if let Some(value) = result {
|
.push(self);
|
||||||
self.push(value);
|
|
||||||
} //NOTE: Natural return is sufficient for custom get
|
|
||||||
} else {
|
} else {
|
||||||
self.push(Value::Undefined);
|
self.push(Value::Undefined);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
//! Activation records
|
//! Activation records
|
||||||
|
|
||||||
use crate::avm1::object::Object;
|
use crate::avm1::object::Object;
|
||||||
|
use crate::avm1::return_value::ReturnValue;
|
||||||
use crate::avm1::scope::Scope;
|
use crate::avm1::scope::Scope;
|
||||||
use crate::avm1::stack_continuation::StackContinuation;
|
use crate::avm1::stack_continuation::StackContinuation;
|
||||||
use crate::avm1::{Avm1, Value};
|
use crate::avm1::{Avm1, Value};
|
||||||
|
@ -261,13 +262,13 @@ impl<'gc> Activation<'gc> {
|
||||||
name: &str,
|
name: &str,
|
||||||
avm: &mut Avm1<'gc>,
|
avm: &mut Avm1<'gc>,
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
) -> Option<Value<'gc>> {
|
) -> ReturnValue<'gc> {
|
||||||
if name == "this" {
|
if name == "this" {
|
||||||
return Some(Value::Object(self.this));
|
return ReturnValue::Immediate(Value::Object(self.this));
|
||||||
}
|
}
|
||||||
|
|
||||||
if name == "arguments" && self.arguments.is_some() {
|
if name == "arguments" && self.arguments.is_some() {
|
||||||
return Some(Value::Object(self.arguments.unwrap()));
|
return ReturnValue::Immediate(Value::Object(self.arguments.unwrap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.scope().resolve(name, avm, context, self.this)
|
self.scope().resolve(name, avm, context, self.this)
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use crate::avm1::activation::Activation;
|
use crate::avm1::activation::Activation;
|
||||||
use crate::avm1::object::{Attribute::*, Object};
|
use crate::avm1::object::{Attribute::*, Object};
|
||||||
|
use crate::avm1::return_value::ReturnValue;
|
||||||
use crate::avm1::scope::Scope;
|
use crate::avm1::scope::Scope;
|
||||||
use crate::avm1::value::Value;
|
use crate::avm1::value::Value;
|
||||||
use crate::avm1::{Avm1, UpdateContext};
|
use crate::avm1::{Avm1, UpdateContext};
|
||||||
|
@ -28,7 +29,7 @@ pub type NativeFunction<'gc> = fn(
|
||||||
&mut UpdateContext<'_, 'gc, '_>,
|
&mut UpdateContext<'_, 'gc, '_>,
|
||||||
GcCell<'gc, Object<'gc>>,
|
GcCell<'gc, Object<'gc>>,
|
||||||
&[Value<'gc>],
|
&[Value<'gc>],
|
||||||
) -> Option<Value<'gc>>;
|
) -> ReturnValue<'gc>;
|
||||||
|
|
||||||
/// Represents a function defined in the AVM1 runtime, either through
|
/// Represents a function defined in the AVM1 runtime, either through
|
||||||
/// `DefineFunction` or `DefineFunction2`.
|
/// `DefineFunction` or `DefineFunction2`.
|
||||||
|
@ -183,7 +184,7 @@ impl<'gc> Executable<'gc> {
|
||||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
this: GcCell<'gc, Object<'gc>>,
|
this: GcCell<'gc, Object<'gc>>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Option<Value<'gc>> {
|
) -> ReturnValue<'gc> {
|
||||||
match self {
|
match self {
|
||||||
Executable::Native(nf) => nf(avm, ac, this, args),
|
Executable::Native(nf) => nf(avm, ac, this, args),
|
||||||
Executable::Action(af) => {
|
Executable::Action(af) => {
|
||||||
|
@ -218,13 +219,17 @@ impl<'gc> Executable<'gc> {
|
||||||
.unwrap_or(ac.player_version)
|
.unwrap_or(ac.player_version)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut frame = Activation::from_function(
|
let frame_cell = GcCell::allocate(
|
||||||
effective_ver,
|
ac.gc_context,
|
||||||
af.data(),
|
Activation::from_function(
|
||||||
child_scope,
|
effective_ver,
|
||||||
this,
|
af.data(),
|
||||||
Some(argcell),
|
child_scope,
|
||||||
|
this,
|
||||||
|
Some(argcell),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
let mut frame = frame_cell.write(ac.gc_context);
|
||||||
|
|
||||||
frame.allocate_local_registers(af.register_count(), ac.gc_context);
|
frame.allocate_local_registers(af.register_count(), ac.gc_context);
|
||||||
|
|
||||||
|
@ -258,12 +263,31 @@ impl<'gc> Executable<'gc> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if af.preload_parent {
|
if af.preload_parent {
|
||||||
let parent = child_scope.read().resolve("_parent", avm, ac, this);
|
let parent_preload_r = preload_r;
|
||||||
if let Some(instant_parent) = parent {
|
|
||||||
frame.set_local_register(preload_r, instant_parent, ac.gc_context);
|
child_scope
|
||||||
} else {
|
.read()
|
||||||
log::error!("User-defined virtual _parent is NOT supported!");
|
.resolve("_parent", avm, ac, this)
|
||||||
}
|
.and_then(
|
||||||
|
avm,
|
||||||
|
ac,
|
||||||
|
stack_continuation!(
|
||||||
|
frame_cell: GcCell<'gc, Activation<'gc>>,
|
||||||
|
parent_preload_r: u8,
|
||||||
|
|_avm, ac, parent| {
|
||||||
|
frame_cell.write(ac.gc_context).set_local_register(
|
||||||
|
*parent_preload_r,
|
||||||
|
parent,
|
||||||
|
ac.gc_context,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(ReturnValue::NoResult)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.ignore();
|
||||||
|
|
||||||
preload_r += 1;
|
preload_r += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,9 +308,9 @@ impl<'gc> Executable<'gc> {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
avm.insert_stack_frame(frame, ac);
|
avm.insert_stack_frame(frame_cell);
|
||||||
|
|
||||||
None
|
ReturnValue::ResultOf(frame_cell)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::avm1::fscommand;
|
use crate::avm1::fscommand;
|
||||||
|
use crate::avm1::return_value::ReturnValue;
|
||||||
use crate::avm1::{Avm1, Object, UpdateContext, Value};
|
use crate::avm1::{Avm1, Object, UpdateContext, Value};
|
||||||
use crate::backend::navigator::NavigationMethod;
|
use crate::backend::navigator::NavigationMethod;
|
||||||
use enumset::EnumSet;
|
use enumset::EnumSet;
|
||||||
|
@ -13,13 +14,13 @@ pub fn getURL<'a, 'gc>(
|
||||||
context: &mut UpdateContext<'a, 'gc, '_>,
|
context: &mut UpdateContext<'a, 'gc, '_>,
|
||||||
_this: GcCell<'gc, Object<'gc>>,
|
_this: GcCell<'gc, Object<'gc>>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Option<Value<'gc>> {
|
) -> ReturnValue<'gc> {
|
||||||
//TODO: Error behavior if no arguments are present
|
//TODO: Error behavior if no arguments are present
|
||||||
if let Some(url_val) = args.get(0) {
|
if let Some(url_val) = args.get(0) {
|
||||||
let url = url_val.clone().into_string();
|
let url = url_val.clone().into_string();
|
||||||
if let Some(fscommand) = fscommand::parse(&url) {
|
if let Some(fscommand) = fscommand::parse(&url) {
|
||||||
fscommand::handle(fscommand, avm, context);
|
fscommand::handle(fscommand, avm, context);
|
||||||
return Some(Value::Undefined);
|
return ReturnValue::Immediate(Value::Undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
let window = args.get(1).map(|v| v.clone().into_string());
|
let window = args.get(1).map(|v| v.clone().into_string());
|
||||||
|
@ -33,7 +34,7 @@ pub fn getURL<'a, 'gc>(
|
||||||
context.navigator.navigate_to_url(url, window, vars_method);
|
context.navigator.navigate_to_url(url, window, vars_method);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(Value::Undefined)
|
ReturnValue::Immediate(Value::Undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn random<'gc>(
|
pub fn random<'gc>(
|
||||||
|
@ -41,12 +42,12 @@ pub fn random<'gc>(
|
||||||
action_context: &mut UpdateContext<'_, 'gc, '_>,
|
action_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
_this: GcCell<'gc, Object<'gc>>,
|
_this: GcCell<'gc, Object<'gc>>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Option<Value<'gc>> {
|
) -> ReturnValue<'gc> {
|
||||||
match args.get(0) {
|
match args.get(0) {
|
||||||
Some(Value::Number(max)) => Some(Value::Number(
|
Some(Value::Number(max)) => ReturnValue::Immediate(Value::Number(
|
||||||
action_context.rng.gen_range(0.0f64, max).floor(),
|
action_context.rng.gen_range(0.0f64, max).floor(),
|
||||||
)),
|
)),
|
||||||
_ => Some(Value::Undefined), //TODO: Shouldn't this be an error condition?
|
_ => ReturnValue::Immediate(Value::Undefined), //TODO: Shouldn't this be an error condition?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,11 +56,11 @@ pub fn boolean<'gc>(
|
||||||
_action_context: &mut UpdateContext<'_, 'gc, '_>,
|
_action_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
_this: GcCell<'gc, Object<'gc>>,
|
_this: GcCell<'gc, Object<'gc>>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Option<Value<'gc>> {
|
) -> ReturnValue<'gc> {
|
||||||
if let Some(val) = args.get(0) {
|
if let Some(val) = args.get(0) {
|
||||||
Some(Value::Bool(val.as_bool(avm.current_swf_version())))
|
ReturnValue::Immediate(Value::Bool(val.as_bool(avm.current_swf_version())))
|
||||||
} else {
|
} else {
|
||||||
Some(Value::Bool(false))
|
ReturnValue::Immediate(Value::Bool(false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,11 +69,11 @@ pub fn number<'gc>(
|
||||||
_action_context: &mut UpdateContext<'_, 'gc, '_>,
|
_action_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
_this: GcCell<'gc, Object<'gc>>,
|
_this: GcCell<'gc, Object<'gc>>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Option<Value<'gc>> {
|
) -> ReturnValue<'gc> {
|
||||||
if let Some(val) = args.get(0) {
|
if let Some(val) = args.get(0) {
|
||||||
Some(Value::Number(val.as_number()))
|
ReturnValue::Immediate(Value::Number(val.as_number()))
|
||||||
} else {
|
} else {
|
||||||
Some(Value::Number(0.0))
|
ReturnValue::Immediate(Value::Number(0.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,11 +82,11 @@ pub fn is_nan<'gc>(
|
||||||
_action_context: &mut UpdateContext<'_, 'gc, '_>,
|
_action_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
_this: GcCell<'gc, Object<'gc>>,
|
_this: GcCell<'gc, Object<'gc>>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Option<Value<'gc>> {
|
) -> ReturnValue<'gc> {
|
||||||
if let Some(val) = args.get(0) {
|
if let Some(val) = args.get(0) {
|
||||||
Some(Value::Bool(val.as_number().is_nan()))
|
ReturnValue::Immediate(Value::Bool(val.as_number().is_nan()))
|
||||||
} else {
|
} else {
|
||||||
Some(Value::Bool(true))
|
ReturnValue::Immediate(Value::Bool(true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +129,7 @@ mod tests {
|
||||||
$(
|
$(
|
||||||
args.push($arg.into());
|
args.push($arg.into());
|
||||||
)*
|
)*
|
||||||
assert_eq!($fun(avm, context, this, &args), Some($out.into()));
|
assert_eq!($fun(avm, context, this, &args), ReturnValue::Immediate($out.into()));
|
||||||
)*
|
)*
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::avm1::object::Attribute::*;
|
use crate::avm1::object::Attribute::*;
|
||||||
|
use crate::avm1::return_value::ReturnValue;
|
||||||
use crate::avm1::{Avm1, Object, UpdateContext, Value};
|
use crate::avm1::{Avm1, Object, UpdateContext, Value};
|
||||||
use gc_arena::{GcCell, MutationContext};
|
use gc_arena::{GcCell, MutationContext};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
@ -9,11 +10,11 @@ macro_rules! wrap_std {
|
||||||
$(
|
$(
|
||||||
$object.force_set_function(
|
$object.force_set_function(
|
||||||
$name,
|
$name,
|
||||||
|_avm, _context, _this, args| -> Option<Value<'gc>> {
|
|_avm, _context, _this, args| -> ReturnValue<'gc> {
|
||||||
if let Some(input) = args.get(0) {
|
if let Some(input) = args.get(0) {
|
||||||
Some(Value::Number($std(input.as_number())))
|
ReturnValue::Immediate(Value::Number($std(input.as_number())))
|
||||||
} else {
|
} else {
|
||||||
Some(Value::Number(NAN))
|
ReturnValue::Immediate(Value::Number(NAN))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
$gc_context,
|
$gc_context,
|
||||||
|
@ -28,15 +29,15 @@ fn atan2<'gc>(
|
||||||
_context: &mut UpdateContext<'_, 'gc, '_>,
|
_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
_this: GcCell<'gc, Object<'gc>>,
|
_this: GcCell<'gc, Object<'gc>>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Option<Value<'gc>> {
|
) -> ReturnValue<'gc> {
|
||||||
if let Some(y) = args.get(0) {
|
if let Some(y) = args.get(0) {
|
||||||
if let Some(x) = args.get(1) {
|
if let Some(x) = args.get(1) {
|
||||||
return Some(Value::Number(y.as_number().atan2(x.as_number())));
|
return ReturnValue::Immediate(Value::Number(y.as_number().atan2(x.as_number())));
|
||||||
} else {
|
} else {
|
||||||
return Some(Value::Number(y.as_number().atan2(0.0)));
|
return ReturnValue::Immediate(Value::Number(y.as_number().atan2(0.0)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(Value::Number(NAN))
|
ReturnValue::Immediate(Value::Number(NAN))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn random<'gc>(
|
pub fn random<'gc>(
|
||||||
|
@ -44,8 +45,8 @@ pub fn random<'gc>(
|
||||||
action_context: &mut UpdateContext<'_, 'gc, '_>,
|
action_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
_this: GcCell<'gc, Object<'gc>>,
|
_this: GcCell<'gc, Object<'gc>>,
|
||||||
_args: &[Value<'gc>],
|
_args: &[Value<'gc>],
|
||||||
) -> Option<Value<'gc>> {
|
) -> ReturnValue<'gc> {
|
||||||
Some(Value::Number(action_context.rng.gen_range(0.0f64, 1.0f64)))
|
ReturnValue::Immediate(Value::Number(action_context.rng.gen_range(0.0f64, 1.0f64)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create<'gc>(gc_context: MutationContext<'gc, '_>) -> GcCell<'gc, Object<'gc>> {
|
pub fn create<'gc>(gc_context: MutationContext<'gc, '_>) -> GcCell<'gc, Object<'gc>> {
|
||||||
|
@ -130,7 +131,7 @@ mod tests {
|
||||||
fn $test() -> Result<(), Error> {
|
fn $test() -> Result<(), Error> {
|
||||||
with_avm(19, |avm, context, _root| {
|
with_avm(19, |avm, context, _root| {
|
||||||
let math = create(context.gc_context);
|
let math = create(context.gc_context);
|
||||||
let function = math.read().get($name, avm, context, math).unwrap();
|
let function = math.read().get($name, avm, context, math).unwrap_immediate();
|
||||||
|
|
||||||
$(
|
$(
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
|
@ -138,7 +139,7 @@ mod tests {
|
||||||
$(
|
$(
|
||||||
args.push($arg.into());
|
args.push($arg.into());
|
||||||
)*
|
)*
|
||||||
assert_eq!(function.call(avm, context, math, &args)?, Some($out.into()));
|
assert_eq!(function.call(avm, context, math, &args)?, ReturnValue::Immediate($out.into()));
|
||||||
)*
|
)*
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -238,7 +239,7 @@ mod tests {
|
||||||
let math = GcCell::allocate(context.gc_context, create(context.gc_context));
|
let math = GcCell::allocate(context.gc_context, create(context.gc_context));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
atan2(avm, context, *math.read(), &[]),
|
atan2(avm, context, *math.read(), &[]),
|
||||||
Some(Value::Number(NAN))
|
ReturnValue::Immediate(Value::Number(NAN))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
atan2(
|
atan2(
|
||||||
|
@ -247,7 +248,7 @@ mod tests {
|
||||||
*math.read(),
|
*math.read(),
|
||||||
&[Value::Number(1.0), Value::Null]
|
&[Value::Number(1.0), Value::Null]
|
||||||
),
|
),
|
||||||
Some(Value::Number(NAN))
|
ReturnValue::Immediate(Value::Number(NAN))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
atan2(
|
atan2(
|
||||||
|
@ -256,7 +257,7 @@ mod tests {
|
||||||
*math.read(),
|
*math.read(),
|
||||||
&[Value::Number(1.0), Value::Undefined]
|
&[Value::Number(1.0), Value::Undefined]
|
||||||
),
|
),
|
||||||
Some(Value::Number(NAN))
|
ReturnValue::Immediate(Value::Number(NAN))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
atan2(
|
atan2(
|
||||||
|
@ -265,7 +266,7 @@ mod tests {
|
||||||
*math.read(),
|
*math.read(),
|
||||||
&[Value::Undefined, Value::Number(1.0)]
|
&[Value::Undefined, Value::Number(1.0)]
|
||||||
),
|
),
|
||||||
Some(Value::Number(NAN))
|
ReturnValue::Immediate(Value::Number(NAN))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -276,7 +277,7 @@ mod tests {
|
||||||
let math = GcCell::allocate(context.gc_context, create(context.gc_context));
|
let math = GcCell::allocate(context.gc_context, create(context.gc_context));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
atan2(avm, context, *math.read(), &[Value::Number(10.0)]),
|
atan2(avm, context, *math.read(), &[Value::Number(10.0)]),
|
||||||
Some(Value::Number(std::f64::consts::FRAC_PI_2))
|
ReturnValue::Immediate(Value::Number(std::f64::consts::FRAC_PI_2))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
atan2(
|
atan2(
|
||||||
|
@ -285,7 +286,7 @@ mod tests {
|
||||||
*math.read(),
|
*math.read(),
|
||||||
&[Value::Number(1.0), Value::Number(2.0)]
|
&[Value::Number(1.0), Value::Number(2.0)]
|
||||||
),
|
),
|
||||||
Some(Value::Number(f64::atan2(1.0, 2.0)))
|
ReturnValue::Immediate(Value::Number(f64::atan2(1.0, 2.0)))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::avm1::function::Executable;
|
use crate::avm1::function::Executable;
|
||||||
use crate::avm1::object::{Attribute::*, Object};
|
use crate::avm1::object::{Attribute::*, Object};
|
||||||
|
use crate::avm1::return_value::ReturnValue;
|
||||||
use crate::avm1::{Avm1, UpdateContext, Value};
|
use crate::avm1::{Avm1, UpdateContext, Value};
|
||||||
use crate::display_object::{DisplayNode, MovieClip};
|
use crate::display_object::{DisplayNode, DisplayObject, MovieClip};
|
||||||
use enumset::EnumSet;
|
use enumset::EnumSet;
|
||||||
use gc_arena::{GcCell, MutationContext};
|
use gc_arena::{GcCell, MutationContext};
|
||||||
|
|
||||||
|
@ -10,13 +11,13 @@ macro_rules! with_movie_clip {
|
||||||
$(
|
$(
|
||||||
$object.force_set_function(
|
$object.force_set_function(
|
||||||
$name,
|
$name,
|
||||||
|_avm, _context, this, args| -> Option<Value<'gc>> {
|
|_avm, _context, this, args| -> ReturnValue<'gc> {
|
||||||
if let Some(display_object) = this.read().display_node() {
|
if let Some(display_object) = this.read().display_node() {
|
||||||
if let Some(movie_clip) = display_object.read().as_movie_clip() {
|
if let Some(movie_clip) = display_object.read().as_movie_clip() {
|
||||||
return Some($fn(movie_clip, args));
|
return ReturnValue::Immediate($fn(movie_clip, args));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(Value::Undefined)
|
ReturnValue::Immediate(Value::Undefined)
|
||||||
},
|
},
|
||||||
$gc_context,
|
$gc_context,
|
||||||
DontDelete | ReadOnly | DontEnum,
|
DontDelete | ReadOnly | DontEnum,
|
||||||
|
@ -30,13 +31,13 @@ macro_rules! with_movie_clip_mut {
|
||||||
$(
|
$(
|
||||||
$object.force_set_function(
|
$object.force_set_function(
|
||||||
$name,
|
$name,
|
||||||
|_avm, context: &mut UpdateContext<'_, 'gc, '_>, this, args| -> Option<Value<'gc>> {
|
|_avm, context: &mut UpdateContext<'_, 'gc, '_>, this, args| -> ReturnValue<'gc> {
|
||||||
if let Some(display_object) = this.read().display_node() {
|
if let Some(display_object) = this.read().display_node() {
|
||||||
if let Some(movie_clip) = display_object.write(context.gc_context).as_movie_clip_mut() {
|
if let Some(movie_clip) = display_object.write(context.gc_context).as_movie_clip_mut() {
|
||||||
return Some($fn(movie_clip, context, display_object, args));
|
return ReturnValue::Immediate($fn(movie_clip, context, display_object, args));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(Value::Undefined)
|
ReturnValue::Immediate(Value::Undefined)
|
||||||
} as crate::avm1::function::NativeFunction<'gc>,
|
} as crate::avm1::function::NativeFunction<'gc>,
|
||||||
$gc_context,
|
$gc_context,
|
||||||
DontDelete | ReadOnly | DontEnum,
|
DontDelete | ReadOnly | DontEnum,
|
||||||
|
@ -50,7 +51,7 @@ pub fn overwrite_root<'gc>(
|
||||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
this: GcCell<'gc, Object<'gc>>,
|
this: GcCell<'gc, Object<'gc>>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Option<Value<'gc>> {
|
) -> ReturnValue<'gc> {
|
||||||
let new_val = args
|
let new_val = args
|
||||||
.get(0)
|
.get(0)
|
||||||
.map(|v| v.to_owned())
|
.map(|v| v.to_owned())
|
||||||
|
@ -58,7 +59,7 @@ pub fn overwrite_root<'gc>(
|
||||||
this.write(ac.gc_context)
|
this.write(ac.gc_context)
|
||||||
.force_set("_root", new_val, EnumSet::new());
|
.force_set("_root", new_val, EnumSet::new());
|
||||||
|
|
||||||
Some(Value::Undefined)
|
ReturnValue::Immediate(Value::Undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn overwrite_global<'gc>(
|
pub fn overwrite_global<'gc>(
|
||||||
|
@ -66,7 +67,7 @@ pub fn overwrite_global<'gc>(
|
||||||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
this: GcCell<'gc, Object<'gc>>,
|
this: GcCell<'gc, Object<'gc>>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Option<Value<'gc>> {
|
) -> ReturnValue<'gc> {
|
||||||
let new_val = args
|
let new_val = args
|
||||||
.get(0)
|
.get(0)
|
||||||
.map(|v| v.to_owned())
|
.map(|v| v.to_owned())
|
||||||
|
@ -74,7 +75,7 @@ pub fn overwrite_global<'gc>(
|
||||||
this.write(ac.gc_context)
|
this.write(ac.gc_context)
|
||||||
.force_set("_global", new_val, EnumSet::new());
|
.force_set("_global", new_val, EnumSet::new());
|
||||||
|
|
||||||
Some(Value::Undefined)
|
ReturnValue::Immediate(Value::Undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_movie_object<'gc>(gc_context: MutationContext<'gc, '_>) -> Object<'gc> {
|
pub fn create_movie_object<'gc>(gc_context: MutationContext<'gc, '_>) -> Object<'gc> {
|
||||||
|
@ -111,19 +112,26 @@ pub fn create_movie_object<'gc>(gc_context: MutationContext<'gc, '_>) -> Object<
|
||||||
"getBytesTotal" => |_movie_clip: &MovieClip<'gc>, _args| {
|
"getBytesTotal" => |_movie_clip: &MovieClip<'gc>, _args| {
|
||||||
// TODO find a correct value
|
// TODO find a correct value
|
||||||
Value::Number(1.0)
|
Value::Number(1.0)
|
||||||
|
},
|
||||||
|
"toString" => |movie_clip: &MovieClip, _args| {
|
||||||
|
Value::String(movie_clip.name().to_string())
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
object.force_set_virtual(
|
object.force_set_virtual(
|
||||||
"_global",
|
"_global",
|
||||||
Executable::Native(|avm, context, _this, _args| Some(avm.global_object(context))),
|
Executable::Native(|avm, context, _this, _args| {
|
||||||
|
ReturnValue::Immediate(avm.global_object(context))
|
||||||
|
}),
|
||||||
Some(Executable::Native(overwrite_global)),
|
Some(Executable::Native(overwrite_global)),
|
||||||
EnumSet::new(),
|
EnumSet::new(),
|
||||||
);
|
);
|
||||||
|
|
||||||
object.force_set_virtual(
|
object.force_set_virtual(
|
||||||
"_root",
|
"_root",
|
||||||
Executable::Native(|avm, context, _this, _args| Some(avm.root_object(context))),
|
Executable::Native(|avm, context, _this, _args| {
|
||||||
|
ReturnValue::Immediate(avm.root_object(context))
|
||||||
|
}),
|
||||||
Some(Executable::Native(overwrite_root)),
|
Some(Executable::Native(overwrite_root)),
|
||||||
EnumSet::new(),
|
EnumSet::new(),
|
||||||
);
|
);
|
||||||
|
@ -131,7 +139,7 @@ pub fn create_movie_object<'gc>(gc_context: MutationContext<'gc, '_>) -> Object<
|
||||||
object.force_set_virtual(
|
object.force_set_virtual(
|
||||||
"_parent",
|
"_parent",
|
||||||
Executable::Native(|_avm, _context, this, _args| {
|
Executable::Native(|_avm, _context, this, _args| {
|
||||||
Some(
|
ReturnValue::Immediate(
|
||||||
this.read()
|
this.read()
|
||||||
.display_node()
|
.display_node()
|
||||||
.and_then(|mc| mc.read().parent())
|
.and_then(|mc| mc.read().parent())
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use self::Attribute::*;
|
use self::Attribute::*;
|
||||||
use crate::avm1::function::{Avm1Function, Executable, NativeFunction};
|
use crate::avm1::function::{Avm1Function, Executable, NativeFunction};
|
||||||
|
use crate::avm1::return_value::ReturnValue;
|
||||||
use crate::avm1::{Avm1, UpdateContext, Value};
|
use crate::avm1::{Avm1, UpdateContext, Value};
|
||||||
use crate::display_object::DisplayNode;
|
use crate::display_object::DisplayNode;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
@ -18,8 +19,8 @@ fn default_to_string<'gc>(
|
||||||
_: &mut UpdateContext<'_, 'gc, '_>,
|
_: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
_: GcCell<'gc, Object<'gc>>,
|
_: GcCell<'gc, Object<'gc>>,
|
||||||
_: &[Value<'gc>],
|
_: &[Value<'gc>],
|
||||||
) -> Option<Value<'gc>> {
|
) -> ReturnValue<'gc> {
|
||||||
Some("[Object object]".into())
|
ReturnValue::Immediate("[Object object]".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(EnumSetType, Debug)]
|
#[derive(EnumSetType, Debug)]
|
||||||
|
@ -53,10 +54,10 @@ impl<'gc> Property<'gc> {
|
||||||
avm: &mut Avm1<'gc>,
|
avm: &mut Avm1<'gc>,
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
this: GcCell<'gc, Object<'gc>>,
|
this: GcCell<'gc, Object<'gc>>,
|
||||||
) -> Option<Value<'gc>> {
|
) -> ReturnValue<'gc> {
|
||||||
match self {
|
match self {
|
||||||
Property::Virtual { get, .. } => get.exec(avm, context, this, &[]),
|
Property::Virtual { get, .. } => get.exec(avm, context, this, &[]),
|
||||||
Property::Stored { value, .. } => Some(value.to_owned()),
|
Property::Stored { value, .. } => ReturnValue::Immediate(value.to_owned()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +78,7 @@ impl<'gc> Property<'gc> {
|
||||||
Property::Virtual { set, .. } => {
|
Property::Virtual { set, .. } => {
|
||||||
if let Some(function) = set {
|
if let Some(function) = set {
|
||||||
let return_value = function.exec(avm, context, this, &[new_value.into()]);
|
let return_value = function.exec(avm, context, this, &[new_value.into()]);
|
||||||
return_value.is_some()
|
return_value.is_immediate()
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -310,11 +311,12 @@ impl<'gc> Object<'gc> {
|
||||||
avm: &mut Avm1<'gc>,
|
avm: &mut Avm1<'gc>,
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
this: GcCell<'gc, Object<'gc>>,
|
this: GcCell<'gc, Object<'gc>>,
|
||||||
) -> Option<Value<'gc>> {
|
) -> ReturnValue<'gc> {
|
||||||
if let Some(value) = self.values.get(name) {
|
if let Some(value) = self.values.get(name) {
|
||||||
return value.get(avm, context, this);
|
return value.get(avm, context, this);
|
||||||
}
|
}
|
||||||
Some(Value::Undefined)
|
|
||||||
|
ReturnValue::Immediate(Value::Undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete a given value off the object.
|
/// Delete a given value off the object.
|
||||||
|
@ -356,11 +358,11 @@ impl<'gc> Object<'gc> {
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
this: GcCell<'gc, Object<'gc>>,
|
this: GcCell<'gc, Object<'gc>>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Option<Value<'gc>> {
|
) -> ReturnValue<'gc> {
|
||||||
if let Some(function) = &self.function {
|
if let Some(function) = &self.function {
|
||||||
function.exec(avm, context, this, args)
|
function.exec(avm, context, this, args)
|
||||||
} else {
|
} else {
|
||||||
Some(Value::Undefined)
|
ReturnValue::Immediate(Value::Undefined)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -437,10 +439,10 @@ mod tests {
|
||||||
let object = GcCell::allocate(gc_context, Object::object(gc_context));
|
let object = GcCell::allocate(gc_context, Object::object(gc_context));
|
||||||
|
|
||||||
let globals = avm.global_object_cell();
|
let globals = avm.global_object_cell();
|
||||||
avm.insert_stack_frame(
|
avm.insert_stack_frame(GcCell::allocate(
|
||||||
|
gc_context,
|
||||||
Activation::from_nothing(swf_version, globals, gc_context),
|
Activation::from_nothing(swf_version, globals, gc_context),
|
||||||
&mut context,
|
));
|
||||||
);
|
|
||||||
|
|
||||||
test(&mut avm, &mut context, object)
|
test(&mut avm, &mut context, object)
|
||||||
})
|
})
|
||||||
|
@ -451,7 +453,7 @@ mod tests {
|
||||||
with_object(0, |avm, context, object| {
|
with_object(0, |avm, context, object| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
object.read().get("not_defined", avm, context, object),
|
object.read().get("not_defined", avm, context, object),
|
||||||
Some(Value::Undefined)
|
ReturnValue::Immediate(Value::Undefined)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -468,11 +470,11 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
object.read().get("forced", avm, context, object),
|
object.read().get("forced", avm, context, object),
|
||||||
Some("forced".into())
|
ReturnValue::Immediate("forced".into())
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
object.read().get("natural", avm, context, object),
|
object.read().get("natural", avm, context, object),
|
||||||
Some("natural".into())
|
ReturnValue::Immediate("natural".into())
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -496,11 +498,11 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
object.read().get("normal", avm, context, object),
|
object.read().get("normal", avm, context, object),
|
||||||
Some("replaced".into())
|
ReturnValue::Immediate("replaced".into())
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
object.read().get("readonly", avm, context, object),
|
object.read().get("readonly", avm, context, object),
|
||||||
Some("initial".into())
|
ReturnValue::Immediate("initial".into())
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -515,7 +517,7 @@ mod tests {
|
||||||
assert_eq!(object.write(context.gc_context).delete("test"), false);
|
assert_eq!(object.write(context.gc_context).delete("test"), false);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
object.read().get("test", avm, context, object),
|
object.read().get("test", avm, context, object),
|
||||||
Some("initial".into())
|
ReturnValue::Immediate("initial".into())
|
||||||
);
|
);
|
||||||
|
|
||||||
object
|
object
|
||||||
|
@ -525,7 +527,7 @@ mod tests {
|
||||||
assert_eq!(object.write(context.gc_context).delete("test"), false);
|
assert_eq!(object.write(context.gc_context).delete("test"), false);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
object.read().get("test", avm, context, object),
|
object.read().get("test", avm, context, object),
|
||||||
Some("replaced".into())
|
ReturnValue::Immediate("replaced".into())
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -533,7 +535,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_virtual_get() {
|
fn test_virtual_get() {
|
||||||
with_object(0, |avm, context, object| {
|
with_object(0, |avm, context, object| {
|
||||||
let getter = Executable::Native(|_avm, _context, _this, _args| Some("Virtual!".into()));
|
let getter = Executable::Native(|_avm, _context, _this, _args| {
|
||||||
|
ReturnValue::Immediate("Virtual!".into())
|
||||||
|
});
|
||||||
|
|
||||||
object.write(context.gc_context).force_set_virtual(
|
object.write(context.gc_context).force_set_virtual(
|
||||||
"test",
|
"test",
|
||||||
|
@ -544,7 +548,7 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
object.read().get("test", avm, context, object),
|
object.read().get("test", avm, context, object),
|
||||||
Some("Virtual!".into())
|
ReturnValue::Immediate("Virtual!".into())
|
||||||
);
|
);
|
||||||
|
|
||||||
// This set should do nothing
|
// This set should do nothing
|
||||||
|
@ -553,7 +557,7 @@ mod tests {
|
||||||
.set("test", "Ignored!", avm, context, object);
|
.set("test", "Ignored!", avm, context, object);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
object.read().get("test", avm, context, object),
|
object.read().get("test", avm, context, object),
|
||||||
Some("Virtual!".into())
|
ReturnValue::Immediate("Virtual!".into())
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -561,7 +565,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete() {
|
fn test_delete() {
|
||||||
with_object(0, |avm, context, object| {
|
with_object(0, |avm, context, object| {
|
||||||
let getter = Executable::Native(|_avm, _context, _this, _args| Some("Virtual!".into()));
|
let getter = Executable::Native(|_avm, _context, _this, _args| {
|
||||||
|
ReturnValue::Immediate("Virtual!".into())
|
||||||
|
});
|
||||||
|
|
||||||
object.write(context.gc_context).force_set_virtual(
|
object.write(context.gc_context).force_set_virtual(
|
||||||
"virtual",
|
"virtual",
|
||||||
|
@ -593,19 +599,19 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
object.read().get("virtual", avm, context, object),
|
object.read().get("virtual", avm, context, object),
|
||||||
Some(Value::Undefined)
|
ReturnValue::Immediate(Value::Undefined)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
object.read().get("virtual_un", avm, context, object),
|
object.read().get("virtual_un", avm, context, object),
|
||||||
Some("Virtual!".into())
|
ReturnValue::Immediate("Virtual!".into())
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
object.read().get("stored", avm, context, object),
|
object.read().get("stored", avm, context, object),
|
||||||
Some(Value::Undefined)
|
ReturnValue::Immediate(Value::Undefined)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
object.read().get("stored_un", avm, context, object),
|
object.read().get("stored_un", avm, context, object),
|
||||||
Some("Stored!".into())
|
ReturnValue::Immediate("Stored!".into())
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -613,7 +619,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_iter_values() {
|
fn test_iter_values() {
|
||||||
with_object(0, |_avm, context, object| {
|
with_object(0, |_avm, context, object| {
|
||||||
let getter = Executable::Native(|_avm, _context, _this, _args| Some(Value::Null));
|
let getter = Executable::Native(|_avm, _context, _this, _args| {
|
||||||
|
ReturnValue::Immediate(Value::Null)
|
||||||
|
});
|
||||||
|
|
||||||
object
|
object
|
||||||
.write(context.gc_context)
|
.write(context.gc_context)
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
//! Return value enum
|
||||||
|
|
||||||
|
use crate::avm1::activation::Activation;
|
||||||
|
use crate::avm1::stack_continuation::StackContinuation;
|
||||||
|
use crate::avm1::{Avm1, Error, Value};
|
||||||
|
use crate::context::UpdateContext;
|
||||||
|
use gc_arena::{Collect, GcCell};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// Represents a value which can be returned immediately or at a later time.
|
||||||
|
#[must_use = "Return values must be used"]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum ReturnValue<'gc> {
|
||||||
|
/// Indicates that the return value is available immediately.
|
||||||
|
Immediate(Value<'gc>),
|
||||||
|
|
||||||
|
/// Indicates that the return value will be calculated on the stack.
|
||||||
|
ResultOf(GcCell<'gc, Activation<'gc>>),
|
||||||
|
|
||||||
|
/// Indicates that there is no value to return.
|
||||||
|
///
|
||||||
|
/// This is primarily intended to signal to the AVM that a given stack
|
||||||
|
/// frame should not cause a value to be pushed to the stack when it
|
||||||
|
/// returns.
|
||||||
|
NoResult,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<'gc> Collect for ReturnValue<'gc> {
|
||||||
|
#[inline]
|
||||||
|
fn trace(&self, cc: gc_arena::CollectionContext) {
|
||||||
|
use ReturnValue::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Immediate(value) => value.trace(cc),
|
||||||
|
ResultOf(frame) => frame.trace(cc),
|
||||||
|
NoResult => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for ReturnValue<'_> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
use ReturnValue::*;
|
||||||
|
|
||||||
|
match (self, other) {
|
||||||
|
(Immediate(val1), Immediate(val2)) => val1 == val2,
|
||||||
|
(ResultOf(frame1), ResultOf(frame2)) => GcCell::ptr_eq(*frame1, *frame2),
|
||||||
|
(NoResult, NoResult) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for ReturnValue<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
use ReturnValue::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Immediate(val) => write!(f, "Immediate({:?})", val),
|
||||||
|
ResultOf(_frame) => write!(f, "ResultOf(<activation frame>)"),
|
||||||
|
NoResult => write!(f, "NoResult"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> ReturnValue<'gc> {
|
||||||
|
/// Run the return value through a stack continuation.
|
||||||
|
///
|
||||||
|
/// If the return value is instant, we call the continuation immediately;
|
||||||
|
/// else we schedule it on the AVM stack. We return a new return value
|
||||||
|
/// representing the most up-to-date state of the computation in question.
|
||||||
|
/// This means it's possible to chain `and_then` functions across multiple
|
||||||
|
/// AVM stack frames.
|
||||||
|
pub fn and_then<F>(
|
||||||
|
self,
|
||||||
|
avm: &mut Avm1<'gc>,
|
||||||
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
mut cont: F,
|
||||||
|
) -> Result<ReturnValue<'gc>, Error>
|
||||||
|
where
|
||||||
|
F: StackContinuation<'gc>,
|
||||||
|
{
|
||||||
|
use ReturnValue::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Immediate(val) => cont.returned(avm, context, val),
|
||||||
|
ResultOf(frame) => {
|
||||||
|
frame.write(context.gc_context).and_then(Box::new(cont));
|
||||||
|
|
||||||
|
//WARNING: This isn't exactly chainable, only one continuation
|
||||||
|
//can run at once.
|
||||||
|
Ok(ResultOf(frame))
|
||||||
|
}
|
||||||
|
NoResult => Ok(NoResult),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark a given return value as intended to be pushed onto the stack.
|
||||||
|
///
|
||||||
|
/// The natural result of a stack frame retiring is to be pushed, so this
|
||||||
|
/// only ensures that Immediate values are pushed.
|
||||||
|
pub fn push(self, avm: &mut Avm1<'gc>) {
|
||||||
|
use ReturnValue::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Immediate(val) => avm.push(val),
|
||||||
|
ResultOf(_frame) => {}
|
||||||
|
NoResult => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes the given return value.
|
||||||
|
///
|
||||||
|
/// This exists primarily so that users of return values can indicate that
|
||||||
|
/// they do not plan to use them.
|
||||||
|
pub fn ignore(self) {}
|
||||||
|
|
||||||
|
pub fn is_immediate(&self) -> bool {
|
||||||
|
use ReturnValue::*;
|
||||||
|
|
||||||
|
if let Immediate(_v) = self {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Panic if a value is not immediate.
|
||||||
|
///
|
||||||
|
/// This should only be used in test assertions.
|
||||||
|
pub fn unwrap_immediate(self) -> Value<'gc> {
|
||||||
|
use ReturnValue::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Immediate(val) => val,
|
||||||
|
_ => panic!("Unwrapped a non-immediate return value"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
//! Represents AVM1 scope chain resolution.
|
//! Represents AVM1 scope chain resolution.
|
||||||
|
|
||||||
|
use crate::avm1::return_value::ReturnValue;
|
||||||
use crate::avm1::{Avm1, Object, UpdateContext, Value};
|
use crate::avm1::{Avm1, Object, UpdateContext, Value};
|
||||||
use enumset::EnumSet;
|
use enumset::EnumSet;
|
||||||
use gc_arena::{GcCell, MutationContext};
|
use gc_arena::{GcCell, MutationContext};
|
||||||
|
@ -246,7 +247,7 @@ impl<'gc> Scope<'gc> {
|
||||||
avm: &mut Avm1<'gc>,
|
avm: &mut Avm1<'gc>,
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
this: GcCell<'gc, Object<'gc>>,
|
this: GcCell<'gc, Object<'gc>>,
|
||||||
) -> Option<Value<'gc>> {
|
) -> ReturnValue<'gc> {
|
||||||
if self.locals().has_property(name) {
|
if self.locals().has_property(name) {
|
||||||
return self.locals().get(name, avm, context, this);
|
return self.locals().get(name, avm, context, this);
|
||||||
}
|
}
|
||||||
|
@ -254,7 +255,7 @@ impl<'gc> Scope<'gc> {
|
||||||
return scope.resolve(name, avm, context, this);
|
return scope.resolve(name, avm, context, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(Value::Undefined)
|
ReturnValue::Immediate(Value::Undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a particular property in the scope chain is defined.
|
/// Check if a particular property in the scope chain is defined.
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
//! GC-compatible scope continuations
|
//! GC-compatible scope continuations
|
||||||
|
|
||||||
|
use crate::avm1::return_value::ReturnValue;
|
||||||
use crate::avm1::{Avm1, Error, Value};
|
use crate::avm1::{Avm1, Error, Value};
|
||||||
use crate::context::UpdateContext;
|
use crate::context::UpdateContext;
|
||||||
use gc_arena::Collect;
|
use gc_arena::Collect;
|
||||||
|
@ -14,12 +15,16 @@ pub trait StackContinuation<'gc>: 'gc + Collect {
|
||||||
/// You are free to use it as you please. In general, however, if you intend
|
/// You are free to use it as you please. In general, however, if you intend
|
||||||
/// to return to the previous activation frame, then you should push this
|
/// to return to the previous activation frame, then you should push this
|
||||||
/// return value on the stack.
|
/// return value on the stack.
|
||||||
|
///
|
||||||
|
/// This function returns another ReturnValue, which can be used to signal
|
||||||
|
/// that the given continuation should be run again, to replace the current
|
||||||
|
/// return value, or to suppress the return value.
|
||||||
fn returned(
|
fn returned(
|
||||||
&mut self,
|
&mut self,
|
||||||
avm: &mut Avm1<'gc>,
|
avm: &mut Avm1<'gc>,
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
return_value: Value<'gc>,
|
return_value: Value<'gc>,
|
||||||
) -> Result<(), Error>;
|
) -> Result<ReturnValue<'gc>, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a continuation from some set of garbage-collected values.
|
/// Generate a continuation from some set of garbage-collected values.
|
||||||
|
@ -31,7 +36,9 @@ pub trait StackContinuation<'gc>: 'gc + Collect {
|
||||||
macro_rules! stack_continuation {
|
macro_rules! stack_continuation {
|
||||||
($( $name:ident: $type:ty ),*, | $avmname:ident, $ctxtname:ident, $retvalname:ident | $code:block) => {
|
($( $name:ident: $type:ty ),*, | $avmname:ident, $ctxtname:ident, $retvalname:ident | $code:block) => {
|
||||||
{
|
{
|
||||||
|
use crate::avm1::return_value::ReturnValue;
|
||||||
use crate::avm1::stack_continuation::StackContinuation;
|
use crate::avm1::stack_continuation::StackContinuation;
|
||||||
|
use crate::avm1::Error;
|
||||||
|
|
||||||
struct MyCont<'gc> {
|
struct MyCont<'gc> {
|
||||||
$(
|
$(
|
||||||
|
@ -50,7 +57,7 @@ macro_rules! stack_continuation {
|
||||||
|
|
||||||
impl<'gc> StackContinuation<'gc> for MyCont<'gc> {
|
impl<'gc> StackContinuation<'gc> for MyCont<'gc> {
|
||||||
#[allow(unused_parens)]
|
#[allow(unused_parens)]
|
||||||
fn returned(&mut self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, return_value: Value<'gc>) -> Result<(), Error> {
|
fn returned(&mut self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, return_value: Value<'gc>) -> Result<ReturnValue<'gc>, Error> {
|
||||||
$(
|
$(
|
||||||
let $name = &mut self.$name;
|
let $name = &mut self.$name;
|
||||||
)*
|
)*
|
||||||
|
@ -62,33 +69,7 @@ macro_rules! stack_continuation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let cont = MyCont{$($name),*};
|
MyCont{$($name),*}
|
||||||
|
|
||||||
Box::new(cont)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wait for the result of a `get` to be ready, then call the continuation.
|
|
||||||
#[allow(unused_macros)]
|
|
||||||
macro_rules! and_then {
|
|
||||||
( $value:expr, $avm:expr, $context:expr, $cont:expr) => {
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
use crate::avm1::stack_continuation::StackContinuation;
|
|
||||||
|
|
||||||
let value = $value;
|
|
||||||
let mut continuation = $cont;
|
|
||||||
let avm = $avm;
|
|
||||||
let context = $context;
|
|
||||||
|
|
||||||
if let Some(instant_value) = value {
|
|
||||||
continuation.returned(avm, context, instant_value)?;
|
|
||||||
} else {
|
|
||||||
avm.stack_frames
|
|
||||||
.last()
|
|
||||||
.unwrap()
|
|
||||||
.write(context.gc_context)
|
|
||||||
.and_then(continuation);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,10 +49,10 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
let globals = avm.global_object_cell();
|
let globals = avm.global_object_cell();
|
||||||
avm.insert_stack_frame(
|
avm.insert_stack_frame(GcCell::allocate(
|
||||||
|
gc_context,
|
||||||
Activation::from_nothing(swf_version, globals, gc_context),
|
Activation::from_nothing(swf_version, globals, gc_context),
|
||||||
&mut context,
|
));
|
||||||
);
|
|
||||||
|
|
||||||
let this = root.read().object().as_object().unwrap().to_owned();
|
let this = root.read().object().as_object().unwrap().to_owned();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::avm1::activation::Activation;
|
use crate::avm1::activation::Activation;
|
||||||
use crate::avm1::test_utils::with_avm;
|
use crate::avm1::test_utils::with_avm;
|
||||||
|
use gc_arena::GcCell;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn locals_into_form_values() {
|
fn locals_into_form_values() {
|
||||||
|
@ -15,7 +16,7 @@ fn locals_into_form_values() {
|
||||||
.write(context.gc_context)
|
.write(context.gc_context)
|
||||||
.set("value2", 2.0, avm, context, my_locals);
|
.set("value2", 2.0, avm, context, my_locals);
|
||||||
|
|
||||||
avm.insert_stack_frame(my_activation, context);
|
avm.insert_stack_frame(GcCell::allocate(context.gc_context, my_activation));
|
||||||
|
|
||||||
let my_local_values = avm.locals_into_form_values(context);
|
let my_local_values = avm.locals_into_form_values(context);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::avm1::object::Object;
|
use crate::avm1::object::Object;
|
||||||
|
use crate::avm1::return_value::ReturnValue;
|
||||||
use crate::avm1::{Avm1, Error, UpdateContext};
|
use crate::avm1::{Avm1, Error, UpdateContext};
|
||||||
use gc_arena::GcCell;
|
use gc_arena::GcCell;
|
||||||
|
|
||||||
|
@ -257,7 +258,7 @@ impl<'gc> Value<'gc> {
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
this: GcCell<'gc, Object<'gc>>,
|
this: GcCell<'gc, Object<'gc>>,
|
||||||
args: &[Value<'gc>],
|
args: &[Value<'gc>],
|
||||||
) -> Result<Option<Value<'gc>>, Error> {
|
) -> Result<ReturnValue<'gc>, Error> {
|
||||||
if let Value::Object(object) = self {
|
if let Value::Object(object) = self {
|
||||||
Ok(object.read().call(avm, context, this, args))
|
Ok(object.read().call(avm, context, this, args))
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue