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
189
core/src/avm1.rs
189
core/src/avm1.rs
|
@ -1,6 +1,7 @@
|
|||
use crate::avm1::function::Avm1Function;
|
||||
use crate::avm1::globals::create_globals;
|
||||
use crate::avm1::object::Object;
|
||||
use crate::avm1::return_value::ReturnValue::*;
|
||||
use crate::backend::navigator::NavigationMethod;
|
||||
use crate::context::UpdateContext;
|
||||
use crate::prelude::*;
|
||||
|
@ -14,12 +15,15 @@ use swf::avm1::types::{Action, Function};
|
|||
|
||||
use crate::tag_utils::SwfSlice;
|
||||
|
||||
#[macro_use]
|
||||
mod stack_continuation;
|
||||
mod activation;
|
||||
mod fscommand;
|
||||
mod function;
|
||||
mod globals;
|
||||
pub mod movie_clip;
|
||||
pub mod object;
|
||||
mod return_value;
|
||||
mod scope;
|
||||
mod value;
|
||||
|
||||
|
@ -29,9 +33,6 @@ mod test_utils;
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[macro_use]
|
||||
mod stack_continuation;
|
||||
|
||||
use activation::Activation;
|
||||
use scope::Scope;
|
||||
pub use value::Value;
|
||||
|
@ -111,7 +112,9 @@ impl<'gc> Avm1<'gc> {
|
|||
|
||||
for k in keys {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -177,13 +180,8 @@ impl<'gc> Avm1<'gc> {
|
|||
}
|
||||
|
||||
/// Add a stack frame for any arbitrary code.
|
||||
pub fn insert_stack_frame(
|
||||
&mut self,
|
||||
frame: Activation<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
) {
|
||||
self.stack_frames
|
||||
.push(GcCell::allocate(context.gc_context, frame));
|
||||
pub fn insert_stack_frame(&mut self, frame: GcCell<'gc, Activation<'gc>>) {
|
||||
self.stack_frames.push(frame);
|
||||
}
|
||||
|
||||
/// Retrieve the current AVM execution frame.
|
||||
|
@ -275,12 +273,14 @@ impl<'gc> Avm1<'gc> {
|
|||
let can_return = !self.stack_frames.is_empty();
|
||||
|
||||
if let Some(func) = frame.write(context.gc_context).get_then_func() {
|
||||
let is_continuing = func.returned(self, context, return_value)?;
|
||||
if is_continuing {
|
||||
if let Some(fr) = self.current_stack_frame() {
|
||||
fr.write(context.gc_context)
|
||||
match func.returned(self, context, return_value)? {
|
||||
Immediate(val) => self.stack.push(val),
|
||||
ResultOf(new_frame) => {
|
||||
new_frame
|
||||
.write(context.gc_context)
|
||||
.and_again(frame, context.gc_context);
|
||||
}
|
||||
NoResult => {}
|
||||
}
|
||||
} else if can_return {
|
||||
self.stack.push(return_value);
|
||||
|
@ -694,29 +694,17 @@ impl<'gc> Avm1<'gc> {
|
|||
);
|
||||
let this = context.active_clip.read().object().as_object()?.to_owned();
|
||||
|
||||
if let Some(target_fn) = target_fn {
|
||||
let return_value = target_fn.call(self, context, this, &args)?;
|
||||
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!(
|
||||
target_fn
|
||||
.and_then(
|
||||
self,
|
||||
context,
|
||||
stack_continuation!(
|
||||
this: GcCell<'gc, Object<'gc>>,
|
||||
args: Vec<Value<'gc>>,
|
||||
|avm, context, target_fn| {
|
||||
let return_value = target_fn.call(avm, context, *this, &args)?;
|
||||
if let Some(instant_return) = return_value {
|
||||
avm.push(instant_return);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
))
|
||||
}
|
||||
|avm, context, target_fn| { target_fn.call(avm, context, *this, &args) }
|
||||
),
|
||||
)?
|
||||
.push(self);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -736,47 +724,26 @@ impl<'gc> Avm1<'gc> {
|
|||
match method_name {
|
||||
Value::Undefined | Value::Null => {
|
||||
let this = context.active_clip.read().object().as_object()?.to_owned();
|
||||
let return_value = object.call(self, context, this, &args)?;
|
||||
if let Some(instant_return) = return_value {
|
||||
self.push(instant_return);
|
||||
}
|
||||
object.call(self, context, this, &args)?.push(self);
|
||||
}
|
||||
Value::String(name) => {
|
||||
if name.is_empty() {
|
||||
let return_value =
|
||||
object.call(self, context, object.as_object()?.to_owned(), &args)?;
|
||||
if let Some(instant_return) = return_value {
|
||||
self.push(instant_return);
|
||||
}
|
||||
object
|
||||
.call(self, context, object.as_object()?.to_owned(), &args)?
|
||||
.push(self);
|
||||
} else {
|
||||
let callable = object.as_object()?.read().get(
|
||||
&name,
|
||||
let target = object.as_object()?.to_owned();
|
||||
object
|
||||
.as_object()?
|
||||
.read()
|
||||
.get(&name, self, context, target)
|
||||
.and_then(
|
||||
self,
|
||||
context,
|
||||
object.as_object()?.to_owned(),
|
||||
);
|
||||
|
||||
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,
|
||||
context,
|
||||
object.as_object()?.to_owned(),
|
||||
&args,
|
||||
)?;
|
||||
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>>,
|
||||
stack_continuation!(
|
||||
target: GcCell<'gc, Object<'gc>>,
|
||||
name: String,
|
||||
object: Value<'gc>,
|
||||
args: Vec<Value<'gc>>,
|
||||
|avm, context, callable| {
|
||||
if let Value::Undefined = callable {
|
||||
return Err(format!(
|
||||
|
@ -786,19 +753,11 @@ impl<'gc> Avm1<'gc> {
|
|||
.into());
|
||||
}
|
||||
|
||||
let return_value = callable.call(
|
||||
avm,
|
||||
context,
|
||||
object.as_object()?.to_owned(),
|
||||
&args,
|
||||
)?;
|
||||
if let Some(instant_return) = return_value {
|
||||
avm.push(instant_return)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
));
|
||||
callable.call(avm, context, *target, &args)
|
||||
}
|
||||
),
|
||||
)?
|
||||
.push(self);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
@ -972,49 +931,31 @@ impl<'gc> Avm1<'gc> {
|
|||
let name_value = self.pop()?;
|
||||
let name = name_value.as_string()?;
|
||||
self.push(Value::Null); // Sentinel that indicates end of enumeration
|
||||
match self
|
||||
.current_stack_frame()
|
||||
self.current_stack_frame()
|
||||
.unwrap()
|
||||
.read()
|
||||
.resolve(name, self, context)
|
||||
{
|
||||
Some(Value::Object(ob)) => {
|
||||
for k in ob.read().get_keys() {
|
||||
self.push(Value::String(k));
|
||||
}
|
||||
}
|
||||
Some(_) => {
|
||||
log::error!("Cannot enumerate properties of {}", name);
|
||||
|
||||
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 {
|
||||
.and_then(
|
||||
self,
|
||||
context,
|
||||
stack_continuation!(name_value: Value<'gc>, |avm, _context, object| {
|
||||
match object {
|
||||
Value::Object(ob) => {
|
||||
for k in ob.read().get_keys() {
|
||||
avm.push(Value::String(k));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
log::error!(
|
||||
_ => log::error!(
|
||||
"Cannot enumerate properties of {}",
|
||||
name_value.as_string()?
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
)),
|
||||
),
|
||||
};
|
||||
|
||||
Ok(ReturnValue::NoResult)
|
||||
}),
|
||||
)?
|
||||
.ignore();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1070,10 +1011,7 @@ impl<'gc> Avm1<'gc> {
|
|||
let name = name_val.into_string();
|
||||
let object = self.pop()?.as_object()?;
|
||||
let this = self.current_stack_frame().unwrap().read().this_cell();
|
||||
let value = object.read().get(&name, self, context, this);
|
||||
if let Some(value) = value {
|
||||
self.push(value);
|
||||
}
|
||||
object.read().get(&name, self, context, this).push(self);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1160,10 +1098,10 @@ impl<'gc> Avm1<'gc> {
|
|||
if let Some(clip) = node.read().as_movie_clip() {
|
||||
let object = clip.object().as_object()?;
|
||||
if object.read().has_property(var_name) {
|
||||
let result = object.read().get(var_name, self, context, object);
|
||||
if let Some(value) = result {
|
||||
self.push(value);
|
||||
} //NOTE: Natural return is sufficient for custom get
|
||||
object
|
||||
.read()
|
||||
.get(var_name, self, context, object)
|
||||
.push(self);
|
||||
} else {
|
||||
self.push(Value::Undefined);
|
||||
}
|
||||
|
@ -1172,14 +1110,11 @@ impl<'gc> Avm1<'gc> {
|
|||
}
|
||||
}
|
||||
} else if self.current_stack_frame().unwrap().read().is_defined(path) {
|
||||
let result = self
|
||||
.current_stack_frame()
|
||||
self.current_stack_frame()
|
||||
.unwrap()
|
||||
.read()
|
||||
.resolve(path, self, context);
|
||||
if let Some(value) = result {
|
||||
self.push(value);
|
||||
} //NOTE: Natural return is sufficient for custom get
|
||||
.resolve(path, self, context)
|
||||
.push(self);
|
||||
} else {
|
||||
self.push(Value::Undefined);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! Activation records
|
||||
|
||||
use crate::avm1::object::Object;
|
||||
use crate::avm1::return_value::ReturnValue;
|
||||
use crate::avm1::scope::Scope;
|
||||
use crate::avm1::stack_continuation::StackContinuation;
|
||||
use crate::avm1::{Avm1, Value};
|
||||
|
@ -261,13 +262,13 @@ impl<'gc> Activation<'gc> {
|
|||
name: &str,
|
||||
avm: &mut Avm1<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
) -> Option<Value<'gc>> {
|
||||
) -> ReturnValue<'gc> {
|
||||
if name == "this" {
|
||||
return Some(Value::Object(self.this));
|
||||
return ReturnValue::Immediate(Value::Object(self.this));
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use crate::avm1::activation::Activation;
|
||||
use crate::avm1::object::{Attribute::*, Object};
|
||||
use crate::avm1::return_value::ReturnValue;
|
||||
use crate::avm1::scope::Scope;
|
||||
use crate::avm1::value::Value;
|
||||
use crate::avm1::{Avm1, UpdateContext};
|
||||
|
@ -28,7 +29,7 @@ pub type NativeFunction<'gc> = fn(
|
|||
&mut UpdateContext<'_, 'gc, '_>,
|
||||
GcCell<'gc, Object<'gc>>,
|
||||
&[Value<'gc>],
|
||||
) -> Option<Value<'gc>>;
|
||||
) -> ReturnValue<'gc>;
|
||||
|
||||
/// Represents a function defined in the AVM1 runtime, either through
|
||||
/// `DefineFunction` or `DefineFunction2`.
|
||||
|
@ -183,7 +184,7 @@ impl<'gc> Executable<'gc> {
|
|||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: GcCell<'gc, Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Option<Value<'gc>> {
|
||||
) -> ReturnValue<'gc> {
|
||||
match self {
|
||||
Executable::Native(nf) => nf(avm, ac, this, args),
|
||||
Executable::Action(af) => {
|
||||
|
@ -218,13 +219,17 @@ impl<'gc> Executable<'gc> {
|
|||
.unwrap_or(ac.player_version)
|
||||
};
|
||||
|
||||
let mut frame = Activation::from_function(
|
||||
let frame_cell = GcCell::allocate(
|
||||
ac.gc_context,
|
||||
Activation::from_function(
|
||||
effective_ver,
|
||||
af.data(),
|
||||
child_scope,
|
||||
this,
|
||||
Some(argcell),
|
||||
),
|
||||
);
|
||||
let mut frame = frame_cell.write(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 {
|
||||
let parent = child_scope.read().resolve("_parent", avm, ac, this);
|
||||
if let Some(instant_parent) = parent {
|
||||
frame.set_local_register(preload_r, instant_parent, ac.gc_context);
|
||||
} else {
|
||||
log::error!("User-defined virtual _parent is NOT supported!");
|
||||
let parent_preload_r = preload_r;
|
||||
|
||||
child_scope
|
||||
.read()
|
||||
.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;
|
||||
}
|
||||
|
||||
|
@ -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::return_value::ReturnValue;
|
||||
use crate::avm1::{Avm1, Object, UpdateContext, Value};
|
||||
use crate::backend::navigator::NavigationMethod;
|
||||
use enumset::EnumSet;
|
||||
|
@ -13,13 +14,13 @@ pub fn getURL<'a, 'gc>(
|
|||
context: &mut UpdateContext<'a, 'gc, '_>,
|
||||
_this: GcCell<'gc, Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Option<Value<'gc>> {
|
||||
) -> ReturnValue<'gc> {
|
||||
//TODO: Error behavior if no arguments are present
|
||||
if let Some(url_val) = args.get(0) {
|
||||
let url = url_val.clone().into_string();
|
||||
if let Some(fscommand) = fscommand::parse(&url) {
|
||||
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());
|
||||
|
@ -33,7 +34,7 @@ pub fn getURL<'a, 'gc>(
|
|||
context.navigator.navigate_to_url(url, window, vars_method);
|
||||
}
|
||||
|
||||
Some(Value::Undefined)
|
||||
ReturnValue::Immediate(Value::Undefined)
|
||||
}
|
||||
|
||||
pub fn random<'gc>(
|
||||
|
@ -41,12 +42,12 @@ pub fn random<'gc>(
|
|||
action_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
_this: GcCell<'gc, Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Option<Value<'gc>> {
|
||||
) -> ReturnValue<'gc> {
|
||||
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(),
|
||||
)),
|
||||
_ => 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, '_>,
|
||||
_this: GcCell<'gc, Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Option<Value<'gc>> {
|
||||
) -> ReturnValue<'gc> {
|
||||
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 {
|
||||
Some(Value::Bool(false))
|
||||
ReturnValue::Immediate(Value::Bool(false))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,11 +69,11 @@ pub fn number<'gc>(
|
|||
_action_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
_this: GcCell<'gc, Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Option<Value<'gc>> {
|
||||
) -> ReturnValue<'gc> {
|
||||
if let Some(val) = args.get(0) {
|
||||
Some(Value::Number(val.as_number()))
|
||||
ReturnValue::Immediate(Value::Number(val.as_number()))
|
||||
} 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, '_>,
|
||||
_this: GcCell<'gc, Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Option<Value<'gc>> {
|
||||
) -> ReturnValue<'gc> {
|
||||
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 {
|
||||
Some(Value::Bool(true))
|
||||
ReturnValue::Immediate(Value::Bool(true))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,7 +129,7 @@ mod tests {
|
|||
$(
|
||||
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(())
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::avm1::object::Attribute::*;
|
||||
use crate::avm1::return_value::ReturnValue;
|
||||
use crate::avm1::{Avm1, Object, UpdateContext, Value};
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
use rand::Rng;
|
||||
|
@ -9,11 +10,11 @@ macro_rules! wrap_std {
|
|||
$(
|
||||
$object.force_set_function(
|
||||
$name,
|
||||
|_avm, _context, _this, args| -> Option<Value<'gc>> {
|
||||
|_avm, _context, _this, args| -> ReturnValue<'gc> {
|
||||
if let Some(input) = args.get(0) {
|
||||
Some(Value::Number($std(input.as_number())))
|
||||
ReturnValue::Immediate(Value::Number($std(input.as_number())))
|
||||
} else {
|
||||
Some(Value::Number(NAN))
|
||||
ReturnValue::Immediate(Value::Number(NAN))
|
||||
}
|
||||
},
|
||||
$gc_context,
|
||||
|
@ -28,15 +29,15 @@ fn atan2<'gc>(
|
|||
_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
_this: GcCell<'gc, Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Option<Value<'gc>> {
|
||||
) -> ReturnValue<'gc> {
|
||||
if let Some(y) = args.get(0) {
|
||||
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 {
|
||||
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>(
|
||||
|
@ -44,8 +45,8 @@ pub fn random<'gc>(
|
|||
action_context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
_this: GcCell<'gc, Object<'gc>>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Option<Value<'gc>> {
|
||||
Some(Value::Number(action_context.rng.gen_range(0.0f64, 1.0f64)))
|
||||
) -> ReturnValue<'gc> {
|
||||
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>> {
|
||||
|
@ -130,7 +131,7 @@ mod tests {
|
|||
fn $test() -> Result<(), Error> {
|
||||
with_avm(19, |avm, context, _root| {
|
||||
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)]
|
||||
|
@ -138,7 +139,7 @@ mod tests {
|
|||
$(
|
||||
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(())
|
||||
|
@ -238,7 +239,7 @@ mod tests {
|
|||
let math = GcCell::allocate(context.gc_context, create(context.gc_context));
|
||||
assert_eq!(
|
||||
atan2(avm, context, *math.read(), &[]),
|
||||
Some(Value::Number(NAN))
|
||||
ReturnValue::Immediate(Value::Number(NAN))
|
||||
);
|
||||
assert_eq!(
|
||||
atan2(
|
||||
|
@ -247,7 +248,7 @@ mod tests {
|
|||
*math.read(),
|
||||
&[Value::Number(1.0), Value::Null]
|
||||
),
|
||||
Some(Value::Number(NAN))
|
||||
ReturnValue::Immediate(Value::Number(NAN))
|
||||
);
|
||||
assert_eq!(
|
||||
atan2(
|
||||
|
@ -256,7 +257,7 @@ mod tests {
|
|||
*math.read(),
|
||||
&[Value::Number(1.0), Value::Undefined]
|
||||
),
|
||||
Some(Value::Number(NAN))
|
||||
ReturnValue::Immediate(Value::Number(NAN))
|
||||
);
|
||||
assert_eq!(
|
||||
atan2(
|
||||
|
@ -265,7 +266,7 @@ mod tests {
|
|||
*math.read(),
|
||||
&[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));
|
||||
assert_eq!(
|
||||
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!(
|
||||
atan2(
|
||||
|
@ -285,7 +286,7 @@ mod tests {
|
|||
*math.read(),
|
||||
&[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::object::{Attribute::*, Object};
|
||||
use crate::avm1::return_value::ReturnValue;
|
||||
use crate::avm1::{Avm1, UpdateContext, Value};
|
||||
use crate::display_object::{DisplayNode, MovieClip};
|
||||
use crate::display_object::{DisplayNode, DisplayObject, MovieClip};
|
||||
use enumset::EnumSet;
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
|
||||
|
@ -10,13 +11,13 @@ macro_rules! with_movie_clip {
|
|||
$(
|
||||
$object.force_set_function(
|
||||
$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(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,
|
||||
DontDelete | ReadOnly | DontEnum,
|
||||
|
@ -30,13 +31,13 @@ macro_rules! with_movie_clip_mut {
|
|||
$(
|
||||
$object.force_set_function(
|
||||
$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(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>,
|
||||
$gc_context,
|
||||
DontDelete | ReadOnly | DontEnum,
|
||||
|
@ -50,7 +51,7 @@ pub fn overwrite_root<'gc>(
|
|||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: GcCell<'gc, Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Option<Value<'gc>> {
|
||||
) -> ReturnValue<'gc> {
|
||||
let new_val = args
|
||||
.get(0)
|
||||
.map(|v| v.to_owned())
|
||||
|
@ -58,7 +59,7 @@ pub fn overwrite_root<'gc>(
|
|||
this.write(ac.gc_context)
|
||||
.force_set("_root", new_val, EnumSet::new());
|
||||
|
||||
Some(Value::Undefined)
|
||||
ReturnValue::Immediate(Value::Undefined)
|
||||
}
|
||||
|
||||
pub fn overwrite_global<'gc>(
|
||||
|
@ -66,7 +67,7 @@ pub fn overwrite_global<'gc>(
|
|||
ac: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: GcCell<'gc, Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Option<Value<'gc>> {
|
||||
) -> ReturnValue<'gc> {
|
||||
let new_val = args
|
||||
.get(0)
|
||||
.map(|v| v.to_owned())
|
||||
|
@ -74,7 +75,7 @@ pub fn overwrite_global<'gc>(
|
|||
this.write(ac.gc_context)
|
||||
.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> {
|
||||
|
@ -111,19 +112,26 @@ pub fn create_movie_object<'gc>(gc_context: MutationContext<'gc, '_>) -> Object<
|
|||
"getBytesTotal" => |_movie_clip: &MovieClip<'gc>, _args| {
|
||||
// TODO find a correct value
|
||||
Value::Number(1.0)
|
||||
},
|
||||
"toString" => |movie_clip: &MovieClip, _args| {
|
||||
Value::String(movie_clip.name().to_string())
|
||||
}
|
||||
);
|
||||
|
||||
object.force_set_virtual(
|
||||
"_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)),
|
||||
EnumSet::new(),
|
||||
);
|
||||
|
||||
object.force_set_virtual(
|
||||
"_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)),
|
||||
EnumSet::new(),
|
||||
);
|
||||
|
@ -131,7 +139,7 @@ pub fn create_movie_object<'gc>(gc_context: MutationContext<'gc, '_>) -> Object<
|
|||
object.force_set_virtual(
|
||||
"_parent",
|
||||
Executable::Native(|_avm, _context, this, _args| {
|
||||
Some(
|
||||
ReturnValue::Immediate(
|
||||
this.read()
|
||||
.display_node()
|
||||
.and_then(|mc| mc.read().parent())
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use self::Attribute::*;
|
||||
use crate::avm1::function::{Avm1Function, Executable, NativeFunction};
|
||||
use crate::avm1::return_value::ReturnValue;
|
||||
use crate::avm1::{Avm1, UpdateContext, Value};
|
||||
use crate::display_object::DisplayNode;
|
||||
use core::fmt;
|
||||
|
@ -18,8 +19,8 @@ fn default_to_string<'gc>(
|
|||
_: &mut UpdateContext<'_, 'gc, '_>,
|
||||
_: GcCell<'gc, Object<'gc>>,
|
||||
_: &[Value<'gc>],
|
||||
) -> Option<Value<'gc>> {
|
||||
Some("[Object object]".into())
|
||||
) -> ReturnValue<'gc> {
|
||||
ReturnValue::Immediate("[Object object]".into())
|
||||
}
|
||||
|
||||
#[derive(EnumSetType, Debug)]
|
||||
|
@ -53,10 +54,10 @@ impl<'gc> Property<'gc> {
|
|||
avm: &mut Avm1<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: GcCell<'gc, Object<'gc>>,
|
||||
) -> Option<Value<'gc>> {
|
||||
) -> ReturnValue<'gc> {
|
||||
match self {
|
||||
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, .. } => {
|
||||
if let Some(function) = set {
|
||||
let return_value = function.exec(avm, context, this, &[new_value.into()]);
|
||||
return_value.is_some()
|
||||
return_value.is_immediate()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
@ -310,11 +311,12 @@ impl<'gc> Object<'gc> {
|
|||
avm: &mut Avm1<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: GcCell<'gc, Object<'gc>>,
|
||||
) -> Option<Value<'gc>> {
|
||||
) -> ReturnValue<'gc> {
|
||||
if let Some(value) = self.values.get(name) {
|
||||
return value.get(avm, context, this);
|
||||
}
|
||||
Some(Value::Undefined)
|
||||
|
||||
ReturnValue::Immediate(Value::Undefined)
|
||||
}
|
||||
|
||||
/// Delete a given value off the object.
|
||||
|
@ -356,11 +358,11 @@ impl<'gc> Object<'gc> {
|
|||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: GcCell<'gc, Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Option<Value<'gc>> {
|
||||
) -> ReturnValue<'gc> {
|
||||
if let Some(function) = &self.function {
|
||||
function.exec(avm, context, this, args)
|
||||
} 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 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),
|
||||
&mut context,
|
||||
);
|
||||
));
|
||||
|
||||
test(&mut avm, &mut context, object)
|
||||
})
|
||||
|
@ -451,7 +453,7 @@ mod tests {
|
|||
with_object(0, |avm, context, object| {
|
||||
assert_eq!(
|
||||
object.read().get("not_defined", avm, context, object),
|
||||
Some(Value::Undefined)
|
||||
ReturnValue::Immediate(Value::Undefined)
|
||||
);
|
||||
})
|
||||
}
|
||||
|
@ -468,11 +470,11 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
object.read().get("forced", avm, context, object),
|
||||
Some("forced".into())
|
||||
ReturnValue::Immediate("forced".into())
|
||||
);
|
||||
assert_eq!(
|
||||
object.read().get("natural", avm, context, object),
|
||||
Some("natural".into())
|
||||
ReturnValue::Immediate("natural".into())
|
||||
);
|
||||
})
|
||||
}
|
||||
|
@ -496,11 +498,11 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
object.read().get("normal", avm, context, object),
|
||||
Some("replaced".into())
|
||||
ReturnValue::Immediate("replaced".into())
|
||||
);
|
||||
assert_eq!(
|
||||
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.read().get("test", avm, context, object),
|
||||
Some("initial".into())
|
||||
ReturnValue::Immediate("initial".into())
|
||||
);
|
||||
|
||||
object
|
||||
|
@ -525,7 +527,7 @@ mod tests {
|
|||
assert_eq!(object.write(context.gc_context).delete("test"), false);
|
||||
assert_eq!(
|
||||
object.read().get("test", avm, context, object),
|
||||
Some("replaced".into())
|
||||
ReturnValue::Immediate("replaced".into())
|
||||
);
|
||||
})
|
||||
}
|
||||
|
@ -533,7 +535,9 @@ mod tests {
|
|||
#[test]
|
||||
fn test_virtual_get() {
|
||||
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(
|
||||
"test",
|
||||
|
@ -544,7 +548,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
object.read().get("test", avm, context, object),
|
||||
Some("Virtual!".into())
|
||||
ReturnValue::Immediate("Virtual!".into())
|
||||
);
|
||||
|
||||
// This set should do nothing
|
||||
|
@ -553,7 +557,7 @@ mod tests {
|
|||
.set("test", "Ignored!", avm, context, object);
|
||||
assert_eq!(
|
||||
object.read().get("test", avm, context, object),
|
||||
Some("Virtual!".into())
|
||||
ReturnValue::Immediate("Virtual!".into())
|
||||
);
|
||||
})
|
||||
}
|
||||
|
@ -561,7 +565,9 @@ mod tests {
|
|||
#[test]
|
||||
fn test_delete() {
|
||||
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(
|
||||
"virtual",
|
||||
|
@ -593,19 +599,19 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
object.read().get("virtual", avm, context, object),
|
||||
Some(Value::Undefined)
|
||||
ReturnValue::Immediate(Value::Undefined)
|
||||
);
|
||||
assert_eq!(
|
||||
object.read().get("virtual_un", avm, context, object),
|
||||
Some("Virtual!".into())
|
||||
ReturnValue::Immediate("Virtual!".into())
|
||||
);
|
||||
assert_eq!(
|
||||
object.read().get("stored", avm, context, object),
|
||||
Some(Value::Undefined)
|
||||
ReturnValue::Immediate(Value::Undefined)
|
||||
);
|
||||
assert_eq!(
|
||||
object.read().get("stored_un", avm, context, object),
|
||||
Some("Stored!".into())
|
||||
ReturnValue::Immediate("Stored!".into())
|
||||
);
|
||||
})
|
||||
}
|
||||
|
@ -613,7 +619,9 @@ mod tests {
|
|||
#[test]
|
||||
fn test_iter_values() {
|
||||
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
|
||||
.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.
|
||||
|
||||
use crate::avm1::return_value::ReturnValue;
|
||||
use crate::avm1::{Avm1, Object, UpdateContext, Value};
|
||||
use enumset::EnumSet;
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
|
@ -246,7 +247,7 @@ impl<'gc> Scope<'gc> {
|
|||
avm: &mut Avm1<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: GcCell<'gc, Object<'gc>>,
|
||||
) -> Option<Value<'gc>> {
|
||||
) -> ReturnValue<'gc> {
|
||||
if self.locals().has_property(name) {
|
||||
return self.locals().get(name, avm, context, this);
|
||||
}
|
||||
|
@ -254,7 +255,7 @@ impl<'gc> Scope<'gc> {
|
|||
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.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
//! GC-compatible scope continuations
|
||||
|
||||
use crate::avm1::return_value::ReturnValue;
|
||||
use crate::avm1::{Avm1, Error, Value};
|
||||
use crate::context::UpdateContext;
|
||||
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
|
||||
/// to return to the previous activation frame, then you should push this
|
||||
/// 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(
|
||||
&mut self,
|
||||
avm: &mut Avm1<'gc>,
|
||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
return_value: Value<'gc>,
|
||||
) -> Result<(), Error>;
|
||||
) -> Result<ReturnValue<'gc>, Error>;
|
||||
}
|
||||
|
||||
/// Generate a continuation from some set of garbage-collected values.
|
||||
|
@ -31,7 +36,9 @@ pub trait StackContinuation<'gc>: 'gc + Collect {
|
|||
macro_rules! stack_continuation {
|
||||
($( $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::Error;
|
||||
|
||||
struct MyCont<'gc> {
|
||||
$(
|
||||
|
@ -50,7 +57,7 @@ macro_rules! stack_continuation {
|
|||
|
||||
impl<'gc> StackContinuation<'gc> for MyCont<'gc> {
|
||||
#[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;
|
||||
)*
|
||||
|
@ -62,33 +69,7 @@ macro_rules! stack_continuation {
|
|||
}
|
||||
}
|
||||
|
||||
let cont = 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);
|
||||
MyCont{$($name),*}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -49,10 +49,10 @@ where
|
|||
};
|
||||
|
||||
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),
|
||||
&mut context,
|
||||
);
|
||||
));
|
||||
|
||||
let this = root.read().object().as_object().unwrap().to_owned();
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::avm1::activation::Activation;
|
||||
use crate::avm1::test_utils::with_avm;
|
||||
use gc_arena::GcCell;
|
||||
|
||||
#[test]
|
||||
fn locals_into_form_values() {
|
||||
|
@ -15,7 +16,7 @@ fn locals_into_form_values() {
|
|||
.write(context.gc_context)
|
||||
.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);
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::avm1::object::Object;
|
||||
use crate::avm1::return_value::ReturnValue;
|
||||
use crate::avm1::{Avm1, Error, UpdateContext};
|
||||
use gc_arena::GcCell;
|
||||
|
||||
|
@ -257,7 +258,7 @@ impl<'gc> Value<'gc> {
|
|||
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||
this: GcCell<'gc, Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Option<Value<'gc>>, Error> {
|
||||
) -> Result<ReturnValue<'gc>, Error> {
|
||||
if let Value::Object(object) = self {
|
||||
Ok(object.read().call(avm, context, this, args))
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue