avm2: Impl `Vector.concat`

This commit is contained in:
David Wendt 2021-03-26 22:58:56 -04:00 committed by kmeisthax
parent 96afc5a6c2
commit dba9b18540
4 changed files with 140 additions and 3 deletions

View File

@ -115,6 +115,7 @@ pub struct SystemPrototypes<'gc> {
pub sprite: Object<'gc>, pub sprite: Object<'gc>,
pub simplebutton: Object<'gc>, pub simplebutton: Object<'gc>,
pub regexp: Object<'gc>, pub regexp: Object<'gc>,
pub vector: Object<'gc>,
} }
impl<'gc> SystemPrototypes<'gc> { impl<'gc> SystemPrototypes<'gc> {
@ -164,6 +165,7 @@ impl<'gc> SystemPrototypes<'gc> {
sprite: empty, sprite: empty,
simplebutton: empty, simplebutton: empty,
regexp: empty, regexp: empty,
vector: empty,
} }
} }
} }
@ -204,6 +206,7 @@ pub struct SystemClasses<'gc> {
pub sprite: Object<'gc>, pub sprite: Object<'gc>,
pub simplebutton: Object<'gc>, pub simplebutton: Object<'gc>,
pub regexp: Object<'gc>, pub regexp: Object<'gc>,
pub vector: Object<'gc>,
} }
impl<'gc> SystemClasses<'gc> { impl<'gc> SystemClasses<'gc> {
@ -253,6 +256,7 @@ impl<'gc> SystemClasses<'gc> {
sprite: empty, sprite: empty,
simplebutton: empty, simplebutton: empty,
regexp: empty, regexp: empty,
vector: empty,
} }
} }
} }
@ -499,8 +503,7 @@ pub fn load_player_globals<'gc>(
class(activation, math::create_class(mc), domain, script)?; class(activation, math::create_class(mc), domain, script)?;
avm2_system_class!(regexp, activation, regexp::create_class(mc), domain, script); avm2_system_class!(regexp, activation, regexp::create_class(mc), domain, script);
class(activation, vector::create_class(mc), domain, script)?; avm2_system_class!(vector, activation, vector::create_class(mc), domain, script);
avm2_system_class!(xml, activation, xml::create_class(mc), domain, script); avm2_system_class!(xml, activation, xml::create_class(mc), domain, script);
avm2_system_class!( avm2_system_class!(
xml_list, xml_list,

View File

@ -5,7 +5,7 @@ use crate::avm2::class::{Class, ClassAttributes};
use crate::avm2::globals::NS_VECTOR; use crate::avm2::globals::NS_VECTOR;
use crate::avm2::method::{Method, NativeMethodImpl}; use crate::avm2::method::{Method, NativeMethodImpl};
use crate::avm2::names::{Namespace, QName}; use crate::avm2::names::{Namespace, QName};
use crate::avm2::object::{vector_allocator, Object, TObject}; use crate::avm2::object::{vector_allocator, Object, TObject, VectorObject};
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
use gc_arena::{GcCell, MutationContext}; use gc_arena::{GcCell, MutationContext};
@ -84,6 +84,84 @@ pub fn set_length<'gc>(
Ok(Value::Undefined) Ok(Value::Undefined)
} }
/// `Vector.concat` impl
pub fn concat<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
if let Some(this) = this {
let mut new_vector_storage =
if let Some(vector) = this.as_vector_storage_mut(activation.context.gc_context) {
vector.clone()
} else {
return Err("Not a vector-structured object".into());
};
let my_class = this
.as_class_object()
.ok_or("TypeError: Tried to concat into a bare object")?;
let val_class = new_vector_storage.value_type();
for arg in args.iter().map(|a| a.clone()) {
let arg_obj = arg.coerce_to_object(activation)?;
let arg_class = arg_obj
.as_class()
.ok_or("TypeError: Tried to concat from a bare object")?;
if !arg.is_of_type(activation, my_class)? {
return Err(format!(
"TypeError: Cannot coerce argument of type {:?} to argument of type {:?}",
arg_class.read().name(),
my_class
.as_class()
.ok_or("TypeError: Tried to concat into a bare object")?
.read()
.name()
)
.into());
}
let old_vec = arg_obj.as_vector_storage();
let old_vec: Vec<Option<Value<'gc>>> = if let Some(old_vec) = old_vec {
old_vec.iter().collect()
} else {
continue;
};
for val in old_vec {
if let Some(val) = val {
if let Ok(val_obj) = val.coerce_to_object(activation) {
if !val.is_of_type(activation, val_class)? {
let other_val_class = val_obj
.as_class()
.ok_or("TypeError: Tried to concat a bare object into a Vector")?;
return Err(format!(
"TypeError: Cannot coerce Vector value of type {:?} to type {:?}",
other_val_class.read().name(),
val_class
.as_class()
.ok_or("TypeError: Tried to concat into a bare object")?
.read()
.name()
)
.into());
}
}
let coerced_val = val.coerce_to_type(activation, val_class)?;
new_vector_storage.push(Some(coerced_val));
} else {
new_vector_storage.push(None);
}
}
}
return Ok(VectorObject::from_vector(new_vector_storage, activation)?.into());
}
Ok(Value::Undefined)
}
/// Construct `Sprite`'s class. /// Construct `Sprite`'s class.
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
let class = Class::new( let class = Class::new(
@ -106,5 +184,8 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
)] = &[("length", Some(length), Some(set_length))]; )] = &[("length", Some(length), Some(set_length))];
write.define_public_builtin_instance_properties(mc, PUBLIC_INSTANCE_PROPERTIES); write.define_public_builtin_instance_properties(mc, PUBLIC_INSTANCE_PROPERTIES);
const PUBLIC_INSTANCE_METHODS: &[(&str, NativeMethodImpl)] = &[("concat", concat)];
write.define_public_builtin_instance_methods(mc, PUBLIC_INSTANCE_METHODS);
class class
} }

View File

@ -50,6 +50,40 @@ pub struct VectorObjectData<'gc> {
vector: VectorStorage<'gc>, vector: VectorStorage<'gc>,
} }
impl<'gc> VectorObject<'gc> {
/// Wrap an existing vector in an object.
pub fn from_vector(
vector: VectorStorage<'gc>,
activation: &mut Activation<'_, 'gc, '_>,
) -> Result<Object<'gc>, Error> {
let value_type = vector.value_type();
let vector_class = activation.avm2().classes().vector;
let mut applied_class = vector_class.apply(activation, &[value_type])?;
let applied_proto = applied_class
.get_property(
applied_class,
&QName::new(Namespace::public(), "prototype"),
activation,
)?
.coerce_to_object(activation)?;
let mut object: Object<'gc> = VectorObject(GcCell::allocate(
activation.context.gc_context,
VectorObjectData {
base: ScriptObjectData::base_new(Some(applied_proto), Some(applied_class)),
vector,
},
))
.into();
object.install_instance_traits(activation, applied_class)?;
object.call_native_init(Some(object), &[], activation, Some(applied_class))?;
Ok(object)
}
}
impl<'gc> TObject<'gc> for VectorObject<'gc> { impl<'gc> TObject<'gc> for VectorObject<'gc> {
impl_avm2_custom_object!(base); impl_avm2_custom_object!(base);
impl_avm2_custom_object_instance!(base); impl_avm2_custom_object_instance!(base);

View File

@ -125,4 +125,23 @@ impl<'gc> VectorStorage<'gc> {
.map(|v| *v = value) .map(|v| *v = value)
.ok_or_else(|| format!("RangeError: {} is outside the range of the vector", pos).into()) .ok_or_else(|| format!("RangeError: {} is outside the range of the vector", pos).into())
} }
/// Push a value to the end of the vector.
///
/// This function does no coercion as calling it requires mutably borrowing
/// the vector (and thus it is unwise to reenter the AVM2 runtime to coerce
/// things). You must use the associated `coerce` fn before storing things
/// in the vector.
pub fn push(&mut self, value: Option<Value<'gc>>) {
self.storage.push(value)
}
/// Iterate over vector values.
pub fn iter<'a>(
&'a self,
) -> impl DoubleEndedIterator<Item = Option<Value<'gc>>>
+ ExactSizeIterator<Item = Option<Value<'gc>>>
+ 'a {
self.storage.iter().cloned()
}
} }