Add stub builtins for Object and Function. These are more-or-less identical to the way we did it in AVM1 (e.g. no fancy player globals file)

This commit is contained in:
David Wendt 2020-02-18 22:26:08 -05:00
parent 1945f36dc0
commit 88957b2b3d
8 changed files with 263 additions and 9 deletions

View File

@ -1,6 +1,7 @@
//! ActionScript Virtual Machine 2 (AS3) support
use crate::avm2::activation::{Activation, Avm2ScriptEntry};
use crate::avm2::globals::SystemPrototypes;
use crate::avm2::names::{Multiname, Namespace, QName};
use crate::avm2::object::{Object, TObject};
use crate::avm2::return_value::ReturnValue;
@ -54,15 +55,21 @@ pub struct Avm2<'gc> {
/// Global scope object.
globals: Object<'gc>,
/// System prototypes.
system_prototypes: SystemPrototypes<'gc>,
}
impl<'gc> Avm2<'gc> {
/// Construct a new AVM interpreter.
pub fn new(mc: MutationContext<'gc, '_>) -> Self {
let (globals, system_prototypes) = globals::construct_global_scope(mc);
Self {
stack_frames: Vec::new(),
stack: Vec::new(),
globals: globals::construct_global_scope(mc),
globals,
system_prototypes,
}
}
@ -85,13 +92,12 @@ impl<'gc> Avm2<'gc> {
let scope = Scope::push_scope(None, self.globals(), context.gc_context);
for trait_entry in entrypoint.script().traits.iter() {
//TODO: Actually stick the Function proto here
self.globals.install_trait(
context.gc_context,
entrypoint.abc(),
trait_entry,
Some(scope),
self.globals,
self.system_prototypes.function,
)?;
}

View File

@ -1,7 +1,7 @@
//! AVM2 executables.
use crate::avm2::activation::Activation;
use crate::avm2::names::QName;
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::{Object, ObjectPtr, TObject};
use crate::avm2::return_value::ReturnValue;
use crate::avm2::scope::Scope;
@ -269,6 +269,48 @@ impl<'gc> FunctionObject<'gc> {
))
.into()
}
/// Construct a builtin function object from a Rust function.
pub fn from_builtin(
mc: MutationContext<'gc, '_>,
nf: NativeFunction<'gc>,
fn_proto: Object<'gc>,
) -> Object<'gc> {
FunctionObject(GcCell::allocate(
mc,
FunctionObjectData {
base: ScriptObjectData::base_new(Some(fn_proto)),
exec: nf.into(),
class: None,
},
))
.into()
}
/// Construct a builtin type from a Rust constructor and prototype.
pub fn from_builtin_constr(
mc: MutationContext<'gc, '_>,
constr: NativeFunction<'gc>,
prototype: Object<'gc>,
fn_proto: Object<'gc>,
) -> Result<Object<'gc>, Error> {
let mut base = ScriptObjectData::base_new(Some(fn_proto));
base.install_dynamic_property(
QName::new(Namespace::public_namespace(), "prototype"),
prototype.into(),
)?;
Ok(FunctionObject(GcCell::allocate(
mc,
FunctionObjectData {
base,
exec: constr.into(),
class: None,
},
))
.into())
}
}
impl<'gc> TObject<'gc> for FunctionObject<'gc> {
@ -332,4 +374,17 @@ impl<'gc> TObject<'gc> for FunctionObject<'gc> {
.base
.install_trait(mc, abc, trait_entry, scope, fn_proto)
}
fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>) {
self.0.write(mc).base.install_method(name, function)
}
fn install_dynamic_property(
&mut self,
mc: MutationContext<'gc, '_>,
name: QName,
value: Value<'gc>,
) -> Result<(), Error> {
self.0.write(mc).base.install_dynamic_property(name, value)
}
}

View File

@ -1,11 +1,69 @@
//! Global scope built-ins
use crate::avm2::function::FunctionObject;
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::{Object, TObject};
use crate::avm2::script_object::ScriptObject;
use gc_arena::MutationContext;
use gc_arena::{Collect, MutationContext};
pub fn construct_global_scope<'gc>(mc: MutationContext<'gc, '_>) -> Object<'gc> {
let global_scope = ScriptObject::bare_object(mc);
mod function;
mod object;
/// This structure represents all system builtins' prototypes.
#[derive(Clone, Collect)]
#[collect(no_drop)]
pub struct SystemPrototypes<'gc> {
pub object: Object<'gc>,
pub function: Object<'gc>,
}
/// Construct a new global scope.
///
/// This function returns both the global scope object, as well as all builtin
/// prototypes that other parts of the VM will need to use.
pub fn construct_global_scope<'gc>(
mc: MutationContext<'gc, '_>,
) -> (Object<'gc>, SystemPrototypes<'gc>) {
let mut global_scope = ScriptObject::bare_object(mc);
let object_proto = ScriptObject::bare_object(mc);
let function_proto = function::create_proto(mc, object_proto);
object::fill_proto(mc, object_proto, function_proto);
let system_prototypes = SystemPrototypes {
object: object_proto,
function: function_proto,
};
global_scope
.install_dynamic_property(
mc,
QName::new(Namespace::public_namespace(), "Object"),
FunctionObject::from_builtin_constr(
mc,
object::constructor,
object_proto,
function_proto,
)
.unwrap()
.into(),
)
.unwrap();
global_scope
.install_dynamic_property(
mc,
QName::new(Namespace::public_namespace(), "Function"),
FunctionObject::from_builtin_constr(
mc,
function::constructor,
function_proto,
function_proto,
)
.unwrap()
.into(),
)
.unwrap();
(global_scope, system_prototypes)
}

View File

@ -0,0 +1,50 @@
//! Function builtin and prototype
use crate::avm2::function::FunctionObject;
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::{Object, TObject};
use crate::avm2::return_value::ReturnValue;
use crate::avm2::script_object::ScriptObject;
use crate::avm2::value::Value;
use crate::avm2::{Avm2, Error};
use crate::context::UpdateContext;
use gc_arena::MutationContext;
/// Implements `Function`
pub fn constructor<'gc>(
_avm: &mut Avm2<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(Value::Undefined.into())
}
/// Implements `Function.prototype.toString`
fn to_string<'gc>(
_: &mut Avm2<'gc>,
_: &mut UpdateContext<'_, 'gc, '_>,
_: Object<'gc>,
_: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(ReturnValue::Immediate("[type Function]".into()))
}
/// Partially construct `Function.prototype`.
///
/// `__proto__` and other cross-linked properties of this object will *not*
/// be defined here. The caller of this function is responsible for linking
/// them in order to obtain a valid ECMAScript `Function` prototype. The
/// returned object is also a bare object, which will need to be linked into
/// the prototype of `Object`.
pub fn create_proto<'gc>(gc_context: MutationContext<'gc, '_>, proto: Object<'gc>) -> Object<'gc> {
let mut function_proto = ScriptObject::object(gc_context, proto);
function_proto.install_method(
gc_context,
QName::new(Namespace::public_namespace(), "toString"),
FunctionObject::from_builtin(gc_context, to_string, function_proto),
);
function_proto
}

View File

@ -0,0 +1,34 @@
//! Object builtin and prototype
use crate::avm2::object::Object;
use crate::avm2::return_value::ReturnValue;
use crate::avm2::value::Value;
use crate::avm2::{Avm2, Error};
use crate::context::UpdateContext;
use gc_arena::MutationContext;
/// Implements `Object`
pub fn constructor<'gc>(
_avm: &mut Avm2<'gc>,
_action_context: &mut UpdateContext<'_, 'gc, '_>,
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
Ok(Value::Undefined.into())
}
/// Partially construct `Object.prototype`.
///
/// `__proto__` and other cross-linked properties of this object will *not*
/// be defined here. The caller of this function is responsible for linking
/// them in order to obtain a valid ECMAScript `Object` prototype.
///
/// Since Object and Function are so heavily intertwined, this function does
/// not allocate an object to store either proto. Instead, you must allocate
/// bare objects for both and let this function fill Object for you.
pub fn fill_proto<'gc>(
_gc_context: MutationContext<'gc, '_>,
_object_proto: Object<'gc>,
_fn_proto: Object<'gc>,
) {
}

View File

@ -71,6 +71,13 @@ pub struct QName {
}
impl QName {
pub fn new(ns: Namespace, name: &str) -> Self {
Self {
ns,
name: name.to_string(),
}
}
pub fn qualified(ns: &Namespace, name: &str) -> Self {
Self {
ns: ns.clone(),

View File

@ -9,7 +9,7 @@ use crate::avm2::script_object::ScriptObject;
use crate::avm2::value::Value;
use crate::avm2::{Avm2, Error};
use crate::context::UpdateContext;
use enumset::{EnumSet, EnumSetType};
use enumset::EnumSet;
use gc_arena::{Collect, GcCell, MutationContext};
use ruffle_macros::enum_trait_object;
use std::fmt::Debug;
@ -116,6 +116,17 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
fn_proto: Object<'gc>,
) -> Result<(), Error>;
/// Install a method (not necessarily from an ABC file) on an object.
fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>);
/// Install a dynamic or built-in value property on an object.
fn install_dynamic_property(
&mut self,
mc: MutationContext<'gc, '_>,
name: QName,
value: Value<'gc>,
) -> Result<(), Error>;
/// Call the object.
fn call(
self,

View File

@ -78,6 +78,19 @@ impl<'gc> TObject<'gc> for ScriptObject<'gc> {
.write(mc)
.install_trait(mc, abc, trait_entry, scope, fn_proto)
}
fn install_method(&mut self, mc: MutationContext<'gc, '_>, name: QName, function: Object<'gc>) {
self.0.write(mc).install_method(name, function)
}
fn install_dynamic_property(
&mut self,
mc: MutationContext<'gc, '_>,
name: QName,
value: Value<'gc>,
) -> Result<(), Error> {
self.0.write(mc).install_dynamic_property(name, value)
}
}
impl<'gc> ScriptObject<'gc> {
@ -89,6 +102,15 @@ impl<'gc> ScriptObject<'gc> {
ScriptObject(GcCell::allocate(mc, ScriptObjectData::base_new(None))).into()
}
/// Construct an object with a base class.
pub fn object(mc: MutationContext<'gc, '_>, proto: Object<'gc>) -> Object<'gc> {
ScriptObject(GcCell::allocate(
mc,
ScriptObjectData::base_new(Some(proto)),
))
.into()
}
/// Construct the instance prototype half of a class.
pub fn instance_prototype(
mc: MutationContext<'gc, '_>,
@ -190,7 +212,7 @@ impl<'gc> ScriptObjectData<'gc> {
}
/// Install a method into the object.
fn install_method(&mut self, name: QName, function: Object<'gc>) {
pub fn install_method(&mut self, name: QName, function: Object<'gc>) {
self.values.insert(name, Property::new_method(function));
}
@ -225,4 +247,15 @@ impl<'gc> ScriptObjectData<'gc> {
.unwrap()
.install_virtual_setter(function)
}
pub fn install_dynamic_property(
&mut self,
name: QName,
value: Value<'gc>,
) -> Result<(), Error> {
self.values
.insert(name, Property::new_dynamic_property(value));
Ok(())
}
}