avm2: Implement `removeChildren`.

This also changes the underlying `DisplayObjectContainer` method to accept any type of range. Turns out enum trait objects aren't actually trait objects and don't need to worry about object safety!
This commit is contained in:
David Wendt 2020-11-10 22:58:59 -05:00 committed by Mike Welsh
parent 38fd29ae5f
commit cf0ab2d82f
7 changed files with 110 additions and 17 deletions

View File

@ -11,6 +11,7 @@ use crate::avm2::Error;
use crate::context::UpdateContext; use crate::context::UpdateContext;
use crate::display_object::{DisplayObject, TDisplayObject, TDisplayObjectContainer}; use crate::display_object::{DisplayObject, TDisplayObject, TDisplayObjectContainer};
use gc_arena::{GcCell, MutationContext}; use gc_arena::{GcCell, MutationContext};
use std::cmp::min;
/// Implements `flash.display.DisplayObjectContainer`'s instance constructor. /// Implements `flash.display.DisplayObjectContainer`'s instance constructor.
pub fn instance_init<'gc>( pub fn instance_init<'gc>(
@ -370,6 +371,57 @@ pub fn remove_child_at<'gc>(
Ok(Value::Undefined) Ok(Value::Undefined)
} }
/// Implements `DisplayObjectContainer.removeChildren`
pub fn remove_children<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
if let Some(parent) = this.and_then(|this| this.as_display_object()) {
if let Some(mut ctr) = parent.as_container() {
let from = args
.get(0)
.cloned()
.unwrap_or_else(|| 0.into())
.coerce_to_i32(activation)?;
let to = args
.get(1)
.cloned()
.unwrap_or_else(|| i32::MAX.into())
.coerce_to_i32(activation)?;
if from >= ctr.num_children() as i32 || from < 0 {
return Err(format!(
"RangeError: Starting position {} does not exist in the child list (valid range is 0 to {})",
from,
ctr.num_children()
)
.into());
}
if (to >= ctr.num_children() as i32 || to < 0) && to != i32::MAX {
return Err(format!(
"RangeError: Ending position {} does not exist in the child list (valid range is 0 to {})",
to,
ctr.num_children()
)
.into());
}
if from > to {
return Err(format!("RangeError: Range {} to {} is invalid", from, to).into());
}
ctr.remove_range_of_ids(
&mut activation.context,
from as usize..min(ctr.num_children(), to as usize + 1),
);
}
}
Ok(Value::Undefined)
}
/// Construct `DisplayObjectContainer`'s class. /// Construct `DisplayObjectContainer`'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(
@ -421,6 +473,10 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
QName::new(Namespace::public_namespace(), "removeChildAt"), QName::new(Namespace::public_namespace(), "removeChildAt"),
Method::from_builtin(remove_child_at), Method::from_builtin(remove_child_at),
)); ));
write.define_instance_trait(Trait::from_method(
QName::new(Namespace::public_namespace(), "removeChildren"),
Method::from_builtin(remove_children),
));
class class
} }

View File

@ -10,7 +10,7 @@ use ruffle_macros::enum_trait_object;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::ops::Range; use std::ops::RangeBounds;
#[enum_trait_object( #[enum_trait_object(
#[derive(Clone, Collect, Debug, Copy)] #[derive(Clone, Collect, Debug, Copy)]
@ -113,11 +113,9 @@ pub trait TDisplayObjectContainer<'gc>:
/// Remove a set of children identified by their render list IDs from this /// Remove a set of children identified by their render list IDs from this
/// container's render, depth, and execution lists. /// container's render, depth, and execution lists.
fn remove_range_of_ids( fn remove_range_of_ids<R>(&mut self, context: &mut UpdateContext<'_, 'gc, '_>, range: R)
&mut self, where
context: &mut UpdateContext<'_, 'gc, '_>, R: RangeBounds<usize>;
range: Range<usize>,
);
/// Clear all three lists in the container. /// Clear all three lists in the container.
fn clear(&mut self, context: MutationContext<'gc, '_>); fn clear(&mut self, context: MutationContext<'gc, '_>);
@ -238,11 +236,10 @@ macro_rules! impl_display_object_container {
.remove_child(context, child) .remove_child(context, child)
} }
fn remove_range_of_ids( fn remove_range_of_ids<R>(&mut self, context: &mut UpdateContext<'_, 'gc, '_>, range: R)
&mut self, where
context: &mut UpdateContext<'_, 'gc, '_>, R: RangeBounds<usize>,
range: Range<usize>, {
) {
self.0 self.0
.write(context.gc_context) .write(context.gc_context)
.$field .$field
@ -625,11 +622,10 @@ impl<'gc> ChildContainer<'gc> {
/// Remove a set of children identified by their render list IDs from this /// Remove a set of children identified by their render list IDs from this
/// container's render, depth, and execution lists. /// container's render, depth, and execution lists.
pub fn remove_range_of_ids( pub fn remove_range_of_ids<R>(&mut self, context: &mut UpdateContext<'_, 'gc, '_>, range: R)
&mut self, where
context: &mut UpdateContext<'_, 'gc, '_>, R: RangeBounds<usize>,
range: Range<usize>, {
) {
let removed_list: Vec<DisplayObject<'gc>> = self.render_list.drain(range).collect(); let removed_list: Vec<DisplayObject<'gc>> = self.render_list.drain(range).collect();
for removed in removed_list { for removed in removed_list {

View File

@ -7,7 +7,7 @@ pub use crate::{
impl_display_object, impl_display_object_container, impl_display_object_sansbounds, impl_display_object, impl_display_object_container, impl_display_object_sansbounds,
}; };
pub use log::{error, info, trace, warn}; pub use log::{error, info, trace, warn};
pub use std::ops::Range; pub use std::ops::RangeBounds;
pub use swf::Matrix; pub use swf::Matrix;
pub use swf::{CharacterId, Color, Twips}; pub use swf::{CharacterId, Color, Twips};

View File

@ -426,6 +426,7 @@ swf_tests! {
(as3_displayobjectcontainer_contains, "avm2/displayobjectcontainer_contains", 5), (as3_displayobjectcontainer_contains, "avm2/displayobjectcontainer_contains", 5),
(as3_displayobjectcontainer_getchildindex, "avm2/displayobjectcontainer_getchildindex", 5), (as3_displayobjectcontainer_getchildindex, "avm2/displayobjectcontainer_getchildindex", 5),
(as3_displayobjectcontainer_removechildat, "avm2/displayobjectcontainer_removechildat", 1), (as3_displayobjectcontainer_removechildat, "avm2/displayobjectcontainer_removechildat", 1),
(as3_displayobjectcontainer_removechildren, "avm2/displayobjectcontainer_removechildren", 5),
} }
// TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough.

View File

@ -0,0 +1,40 @@
//var chx = this.getChildAt(0)
//var chy = this.getChildAt(1)
//var chz = this.getChildAt(2)
//this.removeChildren(0, 1)
//this.getChildAt(0) === chz
true
//this.numChildren
1
//this.removeChildren(0,0x7fffffff)
//this.numChildren
0
//var chx = this.getChildAt(0)
//var chy = this.getChildAt(1)
//var chz = this.getChildAt(2)
//this.removeChildren(1)
//this.getChildAt(0) === chx
true
//this.numChildren
1
//this.removeChildren()
//this.numChildren
0
//var chx = this.getChildAt(0)
//var chy = this.getChildAt(1)
//var chz = this.getChildAt(2)
//this.removeChildren(0, 0)
//this.getChildAt(0) === chy
true
//this.getChildAt(1) === chz
true
//this.numChildren
2
//this.removeChildren(0, 0)
//this.getChildAt(0) === chz
true
//this.numChildren
1
//this.removeChildren(0, 0)
//this.numChildren
0