core: Re-fix #1580 as it's fix didn't make it into the `DisplayObjectContainer` refactor the first time

This commit is contained in:
David Wendt 2020-11-20 22:50:41 -05:00 committed by Mike Welsh
parent 8764831533
commit 6d992e239a
3 changed files with 190 additions and 168 deletions

View File

@ -11,7 +11,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::{Bound, RangeBounds}; use std::ops::RangeBounds;
/// The three lists that a display object container is supposed to maintain. /// The three lists that a display object container is supposed to maintain.
#[derive(EnumSetType)] #[derive(EnumSetType)]
@ -219,12 +219,69 @@ macro_rules! impl_display_object_container {
child: DisplayObject<'gc>, child: DisplayObject<'gc>,
depth: Depth, depth: Depth,
) -> Option<DisplayObject<'gc>> { ) -> Option<DisplayObject<'gc>> {
self.0.write(context.gc_context).$field.replace_at_depth( child.set_place_frame(context.gc_context, 0);
context, child.set_depth(context.gc_context, depth);
(*self).into(), child.set_parent(context.gc_context, Some((*self).into()));
child,
depth, let mut write = self.0.write(context.gc_context);
)
let prev_child = write.$field.insert_child_into_depth_list(depth, child);
let removed_child = if let Some(prev_child) = prev_child {
let position = write
.$field
.iter_render_list()
.position(|x| DisplayObject::ptr_eq(x, prev_child))
.unwrap();
if !prev_child.placed_by_script() {
write.$field.replace_id(position, child);
Some(prev_child)
} else {
write.$field.insert_id(position + 1, child);
None
}
} else {
let above = write
.$field
.iter_depth_range((Bound::Excluded(depth), Bound::Unbounded))
.next();
if let Some((_, above_child)) = above {
let position = write
.$field
.iter_render_list()
.position(|x| DisplayObject::ptr_eq(x, above_child))
.unwrap();
write.$field.insert_id(position, child);
None
} else {
write.$field.push_id(child);
None
}
};
if let Some(removed_child) = removed_child {
write
.$field
.remove_child_from_exec_list(context, removed_child);
}
write
.$field
.add_child_to_exec_list(context.gc_context, child);
drop(write);
if let Some(removed_child) = removed_child {
removed_child.unload(context);
removed_child.set_parent(context.gc_context, None);
}
removed_child
} }
fn swap_at_depth( fn swap_at_depth(
@ -278,20 +335,44 @@ macro_rules! impl_display_object_container {
(*self).into() (*self).into()
)); ));
self.0 let mut write = self.0.write(context.gc_context);
.write(context.gc_context)
.$field let removed_from_depth_list = from_lists.contains(Lists::Depth)
.remove_child(context, child, from_lists) && write.$field.remove_child_from_depth_list(child);
let removed_from_render_list = from_lists.contains(Lists::Render)
&& write.$field.remove_child_from_render_list(child);
let removed_from_execution_list = from_lists.contains(Lists::Execution)
&& write.$field.remove_child_from_exec_list(context, child);
drop(write);
if removed_from_execution_list {
child.unload(context);
child.set_parent(context.gc_context, None);
}
removed_from_render_list || removed_from_depth_list || removed_from_execution_list
} }
fn remove_range_of_ids<R>(&mut self, context: &mut UpdateContext<'_, 'gc, '_>, range: R) fn remove_range_of_ids<R>(&mut self, context: &mut UpdateContext<'_, 'gc, '_>, range: R)
where where
R: RangeBounds<usize>, R: RangeBounds<usize>,
{ {
self.0 let mut write = self.0.write(context.gc_context);
.write(context.gc_context) let removed_list: Vec<DisplayObject<'gc>> =
.$field write.$field.drain_render_range(range).collect();
.remove_range_of_ids(context, range)
for removed in removed_list {
write.$field.remove_child_from_depth_list(removed);
write.$field.remove_child_from_exec_list(context, removed);
drop(write);
removed.unload(context);
removed.set_parent(context.gc_context, None);
write = self.0.write(context.gc_context);
}
} }
fn clear(&mut self, gc_context: MutationContext<'gc, '_>) { fn clear(&mut self, gc_context: MutationContext<'gc, '_>) {
@ -366,7 +447,7 @@ impl<'gc> ChildContainer<'gc> {
/// Adds a child to the front of the execution list. /// Adds a child to the front of the execution list.
/// ///
/// This does not affect the render or depth lists. /// This does not affect the render or depth lists.
fn add_child_to_exec_list( pub fn add_child_to_exec_list(
&mut self, &mut self,
gc_context: MutationContext<'gc, '_>, gc_context: MutationContext<'gc, '_>,
child: DisplayObject<'gc>, child: DisplayObject<'gc>,
@ -381,15 +462,24 @@ impl<'gc> ChildContainer<'gc> {
/// Removes a child from the execution list. /// Removes a child from the execution list.
/// ///
/// This does not affect the render or depth lists. /// This returns `true` if the child was successfully removed, and `false`
fn remove_child_from_exec_list( /// if no list alterations were made.
///
/// This does not affect the render or depth lists, nor does it unload the
/// child. You must unload the child yourself in a clean stack frame, as
/// display objects are permitted to run code when unloading. We also don't
/// unset the parent either as that's expected to happen after unloading.
pub fn remove_child_from_exec_list(
&mut self, &mut self,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
child: DisplayObject<'gc>, child: DisplayObject<'gc>,
) { ) -> bool {
// Remove from children linked list. // Remove from children linked list.
let prev = child.prev_sibling(); let prev = child.prev_sibling();
let next = child.next_sibling(); let next = child.next_sibling();
let present_on_execution_list = prev.is_some()
|| next.is_some()
|| (self.exec_list.is_some() && DisplayObject::ptr_eq(self.exec_list.unwrap(), child));
if let Some(prev) = prev { if let Some(prev) = prev {
prev.set_next_sibling(context.gc_context, next); prev.set_next_sibling(context.gc_context, next);
@ -406,15 +496,28 @@ impl<'gc> ChildContainer<'gc> {
self.exec_list = next; self.exec_list = next;
} }
} }
// Flag child as removed.
child.unload(context); present_on_execution_list
} }
/// Remove a child from the display list. /// Add a child to the depth list.
///
/// This returns the child that was previously at that particular depth, if
/// such a child exists. If so, that constitutes removing the child from
/// the depth list.
pub fn insert_child_into_depth_list(
&mut self,
depth: Depth,
child: DisplayObject<'gc>,
) -> Option<DisplayObject<'gc>> {
self.depth_list.insert(depth, child)
}
/// Remove a child from the depth list.
/// ///
/// This returns `true` if the child was successfully removed, and `false` /// This returns `true` if the child was successfully removed, and `false`
/// if no list alterations were made. /// if no list alterations were made.
fn remove_child_from_depth_list(&mut self, child: DisplayObject<'gc>) -> bool { pub fn remove_child_from_depth_list(&mut self, child: DisplayObject<'gc>) -> bool {
if let Some(other_child) = self.depth_list.get(&child.depth()) { if let Some(other_child) = self.depth_list.get(&child.depth()) {
DisplayObject::ptr_eq(*other_child, child) DisplayObject::ptr_eq(*other_child, child)
&& self.depth_list.remove(&child.depth()).is_some() && self.depth_list.remove(&child.depth()).is_some()
@ -423,6 +526,23 @@ impl<'gc> ChildContainer<'gc> {
} }
} }
/// Remove a child from the render list.
///
/// This returns `true` if the child was successfully removed, and `false`
/// if no list alterations were made.
pub fn remove_child_from_render_list(&mut self, child: DisplayObject<'gc>) -> bool {
let render_list_position = self
.render_list
.iter()
.position(|x| DisplayObject::ptr_eq(*x, child));
if let Some(position) = render_list_position {
self.render_list.remove(position);
true
} else {
false
}
}
/// Returns the highest depth in use by this container, or `None` if there /// Returns the highest depth in use by this container, or `None` if there
/// are no children. /// are no children.
pub fn highest_depth(&self) -> Option<Depth> { pub fn highest_depth(&self) -> Option<Depth> {
@ -468,6 +588,22 @@ impl<'gc> ChildContainer<'gc> {
self.render_list.get(id).copied() self.render_list.get(id).copied()
} }
/// Replace a child in the render list with another child in the same
/// position.
pub fn replace_id(&mut self, id: usize, child: DisplayObject<'gc>) {
self.render_list[id] = child;
}
/// Insert a child into the render list at a particular position.
pub fn insert_id(&mut self, id: usize, child: DisplayObject<'gc>) {
self.render_list.insert(id, child);
}
/// Push a child onto the end of the render list.
pub fn push_id(&mut self, child: DisplayObject<'gc>) {
self.render_list.push(child);
}
/// Get the number of children on the render list. /// Get the number of children on the render list.
pub fn num_children(&self) -> usize { pub fn num_children(&self) -> usize {
self.render_list.len() self.render_list.len()
@ -529,74 +665,6 @@ impl<'gc> ChildContainer<'gc> {
self.render_list.swap(id1, id2); self.render_list.swap(id1, id2);
} }
/// Insert a child into the container at a given depth, replacing any child
/// that already exists at the given depth.
///
/// If a child was replaced, this function returns the child that was
/// removed from the container. It should be removed from any other lists
/// maintained by the owner of this container.
///
/// Children that have been placed by scripts will not be removed from the
/// render list, only the depth list. This function will also not return
/// such children.
///
/// `parent` should be the display object that owns this container.
pub fn replace_at_depth(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
parent: DisplayObject<'gc>,
child: DisplayObject<'gc>,
depth: Depth,
) -> Option<DisplayObject<'gc>> {
child.set_place_frame(context.gc_context, 0);
child.set_depth(context.gc_context, depth);
child.set_parent(context.gc_context, Some(parent));
let prev_child = self.depth_list.insert(depth, child);
let removed_child = if let Some(prev_child) = prev_child {
let position = self
.render_list
.iter()
.position(|x| DisplayObject::ptr_eq(*x, prev_child))
.unwrap();
if !prev_child.placed_by_script() {
self.render_list[position] = child;
Some(prev_child)
} else {
self.render_list.insert(position + 1, child);
None
}
} else if let Some((_, above_child)) = self
.depth_list
.range((Bound::Excluded(depth), Bound::Unbounded))
.next()
{
let position = self
.render_list
.iter()
.position(|x| DisplayObject::ptr_eq(*x, *above_child))
.unwrap();
self.render_list.insert(position, child);
None
} else {
self.render_list.push(child);
None
};
if let Some(removed_child) = removed_child {
self.remove_child_from_exec_list(context, removed_child);
}
self.add_child_to_exec_list(context.gc_context, child);
removed_child
}
/// Move an already-inserted child to a new location on the depth list. /// Move an already-inserted child to a new location on the depth list.
/// ///
/// If another child already exists at the target depth, it will be moved /// If another child already exists at the target depth, it will be moved
@ -657,71 +725,6 @@ impl<'gc> ChildContainer<'gc> {
} }
} }
/// Remove a child display object from this container's render, depth, and
/// execution lists.
///
/// If the child was found on any of the container's lists, this function
/// will return `true`.
///
/// You can control which lists a child should be removed from with the
/// `from_lists` parameter. If a list is omitted from `from_lists`, then
/// not only will the child remain, but the return code will also not take
/// it's presence in the list into account.
pub fn remove_child(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
child: DisplayObject<'gc>,
from_lists: EnumSet<Lists>,
) -> bool {
let removed_from_depth_list =
from_lists.contains(Lists::Depth) && self.remove_child_from_depth_list(child);
let removed_from_render_list = if from_lists.contains(Lists::Render) {
let render_list_position = self
.render_list
.iter()
.position(|x| DisplayObject::ptr_eq(*x, child));
if let Some(position) = render_list_position {
self.render_list.remove(position);
true
} else {
false
}
} else {
false
};
let removed_from_execution_list = if from_lists.contains(Lists::Execution) {
let present_on_execution_list = child.prev_sibling().is_none()
|| child.next_sibling().is_none()
|| (self.exec_list.is_some()
&& DisplayObject::ptr_eq(self.exec_list.unwrap(), child));
self.remove_child_from_exec_list(context, child);
child.set_parent(context.gc_context, None);
present_on_execution_list
} else {
false
};
removed_from_render_list || removed_from_depth_list || removed_from_execution_list
}
/// Remove a set of children identified by their render list IDs from this
/// container's render, depth, and execution lists.
pub fn remove_range_of_ids<R>(&mut self, context: &mut UpdateContext<'_, 'gc, '_>, range: R)
where
R: RangeBounds<usize>,
{
let removed_list: Vec<DisplayObject<'gc>> = self.render_list.drain(range).collect();
for removed in removed_list {
self.remove_child_from_depth_list(removed);
self.remove_child_from_exec_list(context, removed);
}
}
/// Remove all children from the container's execution, render, and depth /// Remove all children from the container's execution, render, and depth
/// lists. /// lists.
pub fn clear(&mut self, gc_context: MutationContext<'gc, '_>) { pub fn clear(&mut self, gc_context: MutationContext<'gc, '_>) {
@ -748,6 +751,33 @@ impl<'gc> ChildContainer<'gc> {
) -> impl 'a + Iterator<Item = (Depth, DisplayObject<'gc>)> { ) -> impl 'a + Iterator<Item = (Depth, DisplayObject<'gc>)> {
self.depth_list.iter().map(|(k, v)| (*k, *v)) self.depth_list.iter().map(|(k, v)| (*k, *v))
} }
/// Iter a particular range of depths.
pub fn iter_depth_range<'a, R>(
&'a self,
range: R,
) -> impl 'a + Iterator<Item = (Depth, DisplayObject<'gc>)>
where
R: RangeBounds<Depth>,
{
self.depth_list.range(range).map(|(k, v)| (*k, *v))
}
/// Yield children in the order they are rendered.
pub fn iter_render_list<'a>(&'a self) -> impl 'a + Iterator<Item = DisplayObject<'gc>> {
self.render_list.iter().copied()
}
/// Remove children from the render list and yield them.
pub fn drain_render_range<'a, R>(
&'a mut self,
range: R,
) -> impl 'a + Iterator<Item = DisplayObject<'gc>>
where
R: RangeBounds<usize>,
{
self.render_list.drain(range)
}
} }
pub struct ExecIter<'gc> { pub struct ExecIter<'gc> {

View File

@ -1605,7 +1605,7 @@ impl<'gc> MovieClip<'gc> {
/// Handle a RemoveObject tag when running a goto action. /// Handle a RemoveObject tag when running a goto action.
#[inline] #[inline]
fn goto_remove_object<'a>( fn goto_remove_object<'a>(
self, mut self,
reader: &mut SwfStream<&'a [u8]>, reader: &mut SwfStream<&'a [u8]>,
version: u8, version: u8,
context: &mut UpdateContext<'_, 'gc, '_>, context: &mut UpdateContext<'_, 'gc, '_>,
@ -1631,18 +1631,10 @@ impl<'gc> MovieClip<'gc> {
if let Some(child) = read.container.get_depth(depth) { if let Some(child) = read.container.get_depth(depth) {
if !child.placed_by_script() { if !child.placed_by_script() {
drop(read); drop(read);
self.0.write(context.gc_context).container.remove_child( self.remove_child(context, child, EnumSet::all());
context,
child,
EnumSet::all(),
);
} else { } else {
drop(read); drop(read);
self.0.write(context.gc_context).container.remove_child( self.remove_child(context, child, Lists::Depth.into());
context,
child,
Lists::Depth.into(),
);
} }
} }
} }

View File

@ -8,7 +8,7 @@ pub use crate::{
}; };
pub use enumset::EnumSet; pub use enumset::EnumSet;
pub use log::{error, info, trace, warn}; pub use log::{error, info, trace, warn};
pub use std::ops::RangeBounds; pub use std::ops::{Bound, RangeBounds};
pub use swf::Matrix; pub use swf::Matrix;
pub use swf::{CharacterId, Color, Twips}; pub use swf::{CharacterId, Color, Twips};