Convert our stub implementations of all non-ECMA classes into `Class`es.

This was surprisingly tricky - due to the need to look up superclasses, class trait instantiation requires an active `Activation` and `UpdateContext`. We can't get those during VM instance creation, since the player needs the VM first before it can give it a context to work with. Ergo, we have to tear the global scope initialization in two. At the first possible moment, the player calls a new `load_player_globals` method that initializes all class traits in global scope.
This commit is contained in:
David Wendt 2020-07-07 00:24:16 -04:00
parent 6f284f60eb
commit 3585cf983b
9 changed files with 248 additions and 157 deletions

View File

@ -77,6 +77,11 @@ impl<'gc> Avm2<'gc> {
}
}
pub fn load_player_globals(context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
let mut activation = Activation::from_nothing(context.reborrow());
globals::load_player_globals(&mut activation)
}
/// Return the current set of system prototypes.
pub fn prototypes(&self) -> &SystemPrototypes<'gc> {
&self.system_prototypes

View File

@ -1,14 +1,15 @@
//! Global scope built-ins
use crate::avm2::activation::Activation;
use crate::avm2::class::Class;
use crate::avm2::method::NativeMethod;
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::{FunctionObject, ScriptObject};
use crate::avm2::object::{Object, TObject};
use crate::avm2::object::{FunctionObject, Object, ScriptObject, TObject};
use crate::avm2::r#trait::Trait;
use crate::avm2::string::AvmString;
use crate::avm2::value::Value;
use crate::avm2::Error;
use gc_arena::{Collect, MutationContext};
use gc_arena::{Collect, GcCell, MutationContext};
use std::f64::NAN;
mod boolean;
@ -67,8 +68,8 @@ fn function<'gc>(
.unwrap()
}
/// Add a class builtin to the global scope.
fn class<'gc>(
/// Add an ES3-style builtin to the global scope.
fn oldstyle_class<'gc>(
mc: MutationContext<'gc, '_>,
mut global_scope: Object<'gc>,
package: impl Into<AvmString<'gc>>,
@ -88,6 +89,17 @@ fn class<'gc>(
.unwrap();
}
/// Add a class builtin to the global scope.
fn class<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
mut global_scope: Object<'gc>,
class_def: GcCell<'gc, Class<'gc>>,
) -> Result<(), Error> {
let class_trait = Trait::from_class(class_def);
global_scope.install_trait(activation, class_trait, global_scope)
}
/// Add a builtin constant to the global scope.
fn constant<'gc>(
mc: MutationContext<'gc, '_>,
@ -103,6 +115,14 @@ fn constant<'gc>(
///
/// This function returns both the global scope object, as well as all builtin
/// prototypes that other parts of the VM will need to use.
///
/// Due to a limitation of our type system and our garbage collector, the
/// player needs a valid `Avm2` but cannot provide us an `UpdateContext` yet.
/// As a result, global scope initialization is split into an "oldstyle phase"
/// and a "player-globals phase". This is the former phase, where we initialize
/// as much as we can without an `UpdateContext`. Note that not all
/// `SystemPrototypes` will be necessarily valid at this point in time, and
/// using them right away will result in objects of the wrong type.
pub fn construct_global_scope<'gc>(
mc: MutationContext<'gc, '_>,
) -> (Object<'gc>, SystemPrototypes<'gc>) {
@ -121,7 +141,7 @@ pub fn construct_global_scope<'gc>(
object::fill_proto(mc, object_proto, fn_proto);
class(
oldstyle_class(
mc,
gs,
"",
@ -130,7 +150,7 @@ pub fn construct_global_scope<'gc>(
object_proto,
fn_proto,
);
class(
oldstyle_class(
mc,
gs,
"",
@ -139,7 +159,7 @@ pub fn construct_global_scope<'gc>(
fn_proto,
fn_proto,
);
class(
oldstyle_class(
mc,
gs,
"",
@ -148,7 +168,7 @@ pub fn construct_global_scope<'gc>(
class_proto,
fn_proto,
);
class(
oldstyle_class(
mc,
gs,
"",
@ -157,7 +177,7 @@ pub fn construct_global_scope<'gc>(
string_proto,
fn_proto,
);
class(
oldstyle_class(
mc,
gs,
"",
@ -166,7 +186,7 @@ pub fn construct_global_scope<'gc>(
boolean_proto,
fn_proto,
);
class(
oldstyle_class(
mc,
gs,
"",
@ -175,9 +195,9 @@ pub fn construct_global_scope<'gc>(
number_proto,
fn_proto,
);
class(mc, gs, "", "int", int::constructor, int_proto, fn_proto);
class(mc, gs, "", "uint", uint::constructor, uint_proto, fn_proto);
class(
oldstyle_class(mc, gs, "", "int", int::constructor, int_proto, fn_proto);
oldstyle_class(mc, gs, "", "uint", uint::constructor, uint_proto, fn_proto);
oldstyle_class(
mc,
gs,
"",
@ -192,77 +212,6 @@ pub fn construct_global_scope<'gc>(
constant(mc, gs, "", "NaN", NAN.into());
constant(mc, gs, "", "Infinity", f64::INFINITY.into());
// package `flash.events`
let eventdispatcher_proto =
flash::events::eventdispatcher::create_proto(mc, object_proto, fn_proto);
class(
mc,
gs,
"flash.events",
"EventDispatcher",
flash::events::eventdispatcher::constructor,
eventdispatcher_proto,
fn_proto,
);
// package `flash.display`
let displayobject_proto =
flash::display::displayobject::create_proto(mc, eventdispatcher_proto, fn_proto);
let interactiveobject_proto =
flash::display::interactiveobject::create_proto(mc, displayobject_proto, fn_proto);
let displayobjectcontainer_proto =
flash::display::displayobjectcontainer::create_proto(mc, interactiveobject_proto, fn_proto);
let sprite_proto =
flash::display::sprite::create_proto(mc, displayobjectcontainer_proto, fn_proto);
let movieclip_proto = flash::display::movieclip::create_proto(mc, sprite_proto, fn_proto);
class(
mc,
gs,
"flash.display",
"DisplayObject",
flash::display::displayobject::constructor,
displayobject_proto,
fn_proto,
);
class(
mc,
gs,
"flash.display",
"InteractiveObject",
flash::display::interactiveobject::constructor,
interactiveobject_proto,
fn_proto,
);
class(
mc,
gs,
"flash.display",
"DisplayObjectContainer",
flash::display::displayobjectcontainer::constructor,
sprite_proto,
fn_proto,
);
class(
mc,
gs,
"flash.display",
"Sprite",
flash::display::sprite::constructor,
sprite_proto,
fn_proto,
);
class(
mc,
gs,
"flash.display",
"MovieClip",
flash::display::movieclip::constructor,
movieclip_proto,
fn_proto,
);
let system_prototypes = SystemPrototypes {
object: object_proto,
function: fn_proto,
@ -277,3 +226,49 @@ pub fn construct_global_scope<'gc>(
(gs, system_prototypes)
}
/// Initialize all remaining builtin classes.
///
/// Due to a limitation of our type system and our garbage collector, the
/// player needs a valid `Avm2` but cannot provide us an `UpdateContext` yet.
/// As a result, global scope initialization is split into an "oldstyle phase"
/// and a "player-globals phase". This is the latter phase.
pub fn load_player_globals<'gc>(activation: &mut Activation<'_, 'gc, '_>) -> Result<(), Error> {
let gs = activation.avm2().globals();
// package `flash.events`
class(
activation,
gs,
flash::events::eventdispatcher::create_class(activation.context.gc_context),
)?;
// package `flash.display`
class(
activation,
gs,
flash::display::displayobject::create_class(activation.context.gc_context),
)?;
class(
activation,
gs,
flash::display::interactiveobject::create_class(activation.context.gc_context),
)?;
class(
activation,
gs,
flash::display::displayobjectcontainer::create_class(activation.context.gc_context),
)?;
class(
activation,
gs,
flash::display::sprite::create_class(activation.context.gc_context),
)?;
class(
activation,
gs,
flash::display::movieclip::create_class(activation.context.gc_context),
)?;
Ok(())
}

View File

@ -1,13 +1,16 @@
//! `flash.display.DisplayObject` builtin/prototype
use crate::avm2::activation::Activation;
use crate::avm2::object::{Object, ScriptObject};
use crate::avm2::class::Class;
use crate::avm2::method::Method;
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::Object;
use crate::avm2::value::Value;
use crate::avm2::Error;
use gc_arena::MutationContext;
use gc_arena::{GcCell, MutationContext};
/// Implements `flash.display.DisplayObject`'s constructor.
pub fn constructor<'gc>(
/// Implements `flash.display.DisplayObject`'s instance constructor.
pub fn instance_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
@ -15,12 +18,22 @@ pub fn constructor<'gc>(
Ok(Value::Undefined)
}
/// Construct `DisplayObject.prototype`.
pub fn create_proto<'gc>(
mc: MutationContext<'gc, '_>,
super_proto: Object<'gc>,
_fn_proto: Object<'gc>,
) -> Object<'gc> {
// TODO: Use `StageObject` here.
ScriptObject::object(mc, super_proto)
/// Implements `flash.display.DisplayObject`'s class constructor.
pub fn class_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
Ok(Value::Undefined)
}
/// Construct `DisplayObject`'s class.
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
Class::new(
QName::new(Namespace::package("flash.display"), "DisplayObject"),
Some(QName::new(Namespace::package("flash.events"), "EventDispatcher").into()),
Method::from_builtin(instance_init),
Method::from_builtin(class_init),
mc,
)
}

View File

@ -1,13 +1,16 @@
//! `flash.display.DisplayObjectContainer` builtin/prototype
use crate::avm2::activation::Activation;
use crate::avm2::object::{Object, ScriptObject};
use crate::avm2::class::Class;
use crate::avm2::method::Method;
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::Object;
use crate::avm2::value::Value;
use crate::avm2::Error;
use gc_arena::MutationContext;
use gc_arena::{GcCell, MutationContext};
/// Implements `flash.display.DisplayObjectContainer`'s constructor.
pub fn constructor<'gc>(
/// Implements `flash.display.DisplayObjectContainer`'s instance constructor.
pub fn instance_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
@ -15,12 +18,25 @@ pub fn constructor<'gc>(
Ok(Value::Undefined)
}
/// Construct `DisplayObjectContainer.prototype`.
pub fn create_proto<'gc>(
mc: MutationContext<'gc, '_>,
super_proto: Object<'gc>,
_fn_proto: Object<'gc>,
) -> Object<'gc> {
// TODO: Use `StageObject` here.
ScriptObject::object(mc, super_proto)
/// Implements `flash.display.DisplayObjectContainer`'s class constructor.
pub fn class_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
Ok(Value::Undefined)
}
/// Construct `DisplayObjectContainer`'s class.
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
Class::new(
QName::new(
Namespace::package("flash.display"),
"DisplayObjectContainer",
),
Some(QName::new(Namespace::package("flash.display"), "InteractiveObject").into()),
Method::from_builtin(instance_init),
Method::from_builtin(class_init),
mc,
)
}

View File

@ -1,13 +1,16 @@
//! `flash.display.InteractiveObject` builtin/prototype
use crate::avm2::activation::Activation;
use crate::avm2::object::{Object, ScriptObject};
use crate::avm2::class::Class;
use crate::avm2::method::Method;
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::Object;
use crate::avm2::value::Value;
use crate::avm2::Error;
use gc_arena::MutationContext;
use gc_arena::{GcCell, MutationContext};
/// Implements `flash.display.InteractiveObject`'s constructor.
pub fn constructor<'gc>(
/// Implements `flash.display.InteractiveObject`'s instance constructor.
pub fn instance_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
@ -15,12 +18,22 @@ pub fn constructor<'gc>(
Ok(Value::Undefined)
}
/// Construct `InteractiveObject.prototype`.
pub fn create_proto<'gc>(
mc: MutationContext<'gc, '_>,
super_proto: Object<'gc>,
_fn_proto: Object<'gc>,
) -> Object<'gc> {
// TODO: Use `StageObject` here.
ScriptObject::object(mc, super_proto)
/// Implements `flash.display.InteractiveObject`'s class constructor.
pub fn class_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
Ok(Value::Undefined)
}
/// Construct `InteractiveObject`'s class.
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
Class::new(
QName::new(Namespace::package("flash.display"), "InteractiveObject"),
Some(QName::new(Namespace::package("flash.display"), "DisplayObject").into()),
Method::from_builtin(instance_init),
Method::from_builtin(class_init),
mc,
)
}

View File

@ -1,13 +1,16 @@
//! `flash.display.MovieClip` builtin/prototype
use crate::avm2::activation::Activation;
use crate::avm2::object::{Object, ScriptObject};
use crate::avm2::class::Class;
use crate::avm2::method::Method;
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::Object;
use crate::avm2::value::Value;
use crate::avm2::Error;
use gc_arena::MutationContext;
use gc_arena::{GcCell, MutationContext};
/// Implements `flash.display.MovieClip`'s constructor.
pub fn constructor<'gc>(
/// Implements `flash.display.MovieClip`'s instance constructor.
pub fn instance_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
@ -15,12 +18,22 @@ pub fn constructor<'gc>(
Ok(Value::Undefined)
}
/// Construct `MovieClip.prototype`.
pub fn create_proto<'gc>(
mc: MutationContext<'gc, '_>,
super_proto: Object<'gc>,
_fn_proto: Object<'gc>,
) -> Object<'gc> {
// TODO: Use `StageObject` here.
ScriptObject::object(mc, super_proto)
/// Implements `flash.display.MovieClip`'s class constructor.
pub fn class_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
Ok(Value::Undefined)
}
/// Construct `MovieClip`'s class.
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
Class::new(
QName::new(Namespace::package("flash.display"), "MovieClip"),
Some(QName::new(Namespace::package("flash.display"), "Sprite").into()),
Method::from_builtin(instance_init),
Method::from_builtin(class_init),
mc,
)
}

View File

@ -1,13 +1,16 @@
//! `flash.display.Sprite` builtin/prototype
use crate::avm2::activation::Activation;
use crate::avm2::object::{Object, ScriptObject};
use crate::avm2::class::Class;
use crate::avm2::method::Method;
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::Object;
use crate::avm2::value::Value;
use crate::avm2::Error;
use gc_arena::MutationContext;
use gc_arena::{GcCell, MutationContext};
/// Implements `flash.display.Sprite`'s constructor.
pub fn constructor<'gc>(
/// Implements `flash.display.Sprite`'s instance constructor.
pub fn instance_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
@ -15,12 +18,28 @@ pub fn constructor<'gc>(
Ok(Value::Undefined)
}
/// Construct `Sprite.prototype`.
pub fn create_proto<'gc>(
mc: MutationContext<'gc, '_>,
super_proto: Object<'gc>,
_fn_proto: Object<'gc>,
) -> Object<'gc> {
// TODO: Use `StageObject` here.
ScriptObject::object(mc, super_proto)
/// Implements `flash.display.Sprite`'s class constructor.
pub fn class_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
Ok(Value::Undefined)
}
/// Construct `Sprite`'s class.
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
Class::new(
QName::new(Namespace::package("flash.display"), "Sprite"),
Some(
QName::new(
Namespace::package("flash.display"),
"DisplayObjectContainer",
)
.into(),
),
Method::from_builtin(instance_init),
Method::from_builtin(class_init),
mc,
)
}

View File

@ -1,13 +1,16 @@
//! `flash.events.EventDispatcher` builtin/prototype
use crate::avm2::activation::Activation;
use crate::avm2::object::{Object, ScriptObject};
use crate::avm2::class::Class;
use crate::avm2::method::Method;
use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::Object;
use crate::avm2::value::Value;
use crate::avm2::Error;
use gc_arena::MutationContext;
use gc_arena::{GcCell, MutationContext};
/// Implements `flash.events.EventDispatcher`'s constructor.
pub fn constructor<'gc>(
/// Implements `flash.events.EventDispatcher`'s instance constructor.
pub fn instance_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
@ -15,12 +18,22 @@ pub fn constructor<'gc>(
Ok(Value::Undefined)
}
/// Construct `EventDispatcher.prototype`.
pub fn create_proto<'gc>(
mc: MutationContext<'gc, '_>,
super_proto: Object<'gc>,
_fn_proto: Object<'gc>,
) -> Object<'gc> {
// TODO: Use `StageObject` here.
ScriptObject::object(mc, super_proto)
/// Implements `flash.events.EventDispatcher`'s class constructor.
pub fn class_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
Ok(Value::Undefined)
}
/// Construct `EventDispatcher`'s class.
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
Class::new(
QName::new(Namespace::package("flash.events"), "EventDispatcher"),
Some(QName::new(Namespace::public_namespace(), "Object").into()),
Method::from_builtin(instance_init),
Method::from_builtin(class_init),
mc,
)
}

View File

@ -345,6 +345,10 @@ impl Player {
AvmString::new(activation.context.gc_context, version_string).into(),
EnumSet::empty(),
);
drop(activation);
Avm2::load_player_globals(context).unwrap();
});
self.build_matrices();