avm1: shrink `Value` by boxing `MovieClipReference`s
This doesn't actually cost an extra allocation as we can replace an inner `Gc<Vec<_>>` by a `Box<[_]>` directly.
This commit is contained in:
parent
22040552b8
commit
bb08d356b1
|
@ -5,15 +5,14 @@ use crate::{
|
|||
};
|
||||
use gc_arena::{Collect, Gc, GcCell, GcWeakCell};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Collect)]
|
||||
#[derive(Clone, Debug, Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct MovieClipPath<'gc> {
|
||||
/// The level that this path starts from
|
||||
level: i32,
|
||||
|
||||
/// The elements of the path
|
||||
/// This is wrapped in a `Gc` to make this struct `Copy`, otherwise `Value` couldn't be `Copy`
|
||||
path_segments: Gc<'gc, Vec<AvmString<'gc>>>,
|
||||
path_segments: Box<[AvmString<'gc>]>,
|
||||
|
||||
/// The unparsed, original path
|
||||
/// This wastes some memory, but saves having to re-build it when coercing to a string
|
||||
|
@ -42,10 +41,12 @@ impl<'gc> MovieClipPath<'gc> {
|
|||
.map(|s| AvmString::new(activation.context.gc_context, s))
|
||||
.collect();
|
||||
|
||||
let full_path = AvmString::new(activation.context.gc_context, path);
|
||||
|
||||
Self {
|
||||
level,
|
||||
path_segments: Gc::allocate(activation.context.gc_context, path_segments),
|
||||
full_path: AvmString::new(activation.context.gc_context, path),
|
||||
path_segments,
|
||||
full_path,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,14 +56,18 @@ impl<'gc> MovieClipPath<'gc> {
|
|||
/// This also handles caching to maintain performance
|
||||
#[derive(Copy, Clone, Debug, Collect)]
|
||||
#[collect(no_drop)]
|
||||
pub struct MovieClipReference<'gc> {
|
||||
pub struct MovieClipReference<'gc>(Gc<'gc, MovieClipReferenceData<'gc>>);
|
||||
|
||||
#[derive(Clone, Debug, Collect)]
|
||||
#[collect(no_drop)]
|
||||
struct MovieClipReferenceData<'gc> {
|
||||
/// The path to the target clip
|
||||
pub path: MovieClipPath<'gc>,
|
||||
path: MovieClipPath<'gc>,
|
||||
|
||||
/// A weak reference to the target stage object that `path` points to
|
||||
/// This is used for fast-path resvoling when possible, as well as for re-generating `path` (in the case the the target object is renamed)
|
||||
/// If this is `None` then we have previously missed the cache, due to the target object being removed and re-created, causing us to fallback to the slow path resolution
|
||||
pub cached_stage_object: GcCell<'gc, Option<GcWeakCell<'gc, StageObjectData<'gc>>>>,
|
||||
cached_stage_object: GcCell<'gc, Option<GcWeakCell<'gc, StageObjectData<'gc>>>>,
|
||||
}
|
||||
|
||||
impl<'gc> MovieClipReference<'gc> {
|
||||
|
@ -72,34 +77,29 @@ impl<'gc> MovieClipReference<'gc> {
|
|||
) -> Option<Self> {
|
||||
// We can't use as_display_object + as_movie_clip here as we explicitly don't want to convert `SuperObjects`
|
||||
let display_object = stage_object.as_display_object().unwrap();
|
||||
if let DisplayObject::MovieClip(mc) = display_object {
|
||||
let path = MovieClipPath::new_from_path(activation, mc.path());
|
||||
Some(Self {
|
||||
path,
|
||||
cached_stage_object: GcCell::allocate(
|
||||
activation.context.gc_context,
|
||||
Some(stage_object.as_weak()),
|
||||
),
|
||||
})
|
||||
let (path, cached) = if let DisplayObject::MovieClip(mc) = display_object {
|
||||
(mc.path(), stage_object)
|
||||
} else if activation.swf_version() <= 5 {
|
||||
let display_object = Self::process_swf5_references(activation, display_object)?;
|
||||
|
||||
let stage_object = display_object
|
||||
.object()
|
||||
.coerce_to_object(activation)
|
||||
.as_stage_object()?;
|
||||
let path = MovieClipPath::new_from_path(activation, display_object.path());
|
||||
(display_object.path(), stage_object)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(Self {
|
||||
path,
|
||||
Some(Self(Gc::allocate(
|
||||
activation.context.gc_context,
|
||||
MovieClipReferenceData {
|
||||
path: MovieClipPath::new_from_path(activation, path),
|
||||
cached_stage_object: GcCell::allocate(
|
||||
activation.context.gc_context,
|
||||
Some(stage_object.as_weak()),
|
||||
Some(cached.as_weak()),
|
||||
),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
)))
|
||||
}
|
||||
|
||||
/// Handle the logic of swfv5 DisplayObjects
|
||||
|
@ -128,7 +128,7 @@ impl<'gc> MovieClipReference<'gc> {
|
|||
&self,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
) -> Option<(bool, Object<'gc>, DisplayObject<'gc>)> {
|
||||
let cache = self.cached_stage_object.read();
|
||||
let cache = self.0.cached_stage_object.read();
|
||||
// Check if we have a cache we can use
|
||||
if let Some(cache) = cache.as_ref() {
|
||||
// Check if we can re-use the cached `DisplayObject`, if we can then take this fast path
|
||||
|
@ -155,6 +155,7 @@ impl<'gc> MovieClipReference<'gc> {
|
|||
|
||||
// We missed the cache, switch to always use the slow-path
|
||||
*self
|
||||
.0
|
||||
.cached_stage_object
|
||||
.write(activation.context.gc_context) = None;
|
||||
|
||||
|
@ -168,10 +169,10 @@ impl<'gc> MovieClipReference<'gc> {
|
|||
// Should correctly find the child. As `this` is Value::MovieClip("_level0.child"), we don't want to try and find `123.child`!
|
||||
|
||||
// Get the level
|
||||
let mut start = Some(activation.resolve_level(self.path.level));
|
||||
let mut start = Some(activation.resolve_level(self.0.path.level));
|
||||
|
||||
// Keep traversing to find the target DisplayObject
|
||||
for part in self.path.path_segments.iter() {
|
||||
for part in self.0.path.path_segments.iter() {
|
||||
if let Some(s) = start {
|
||||
if let Some(con) = s.as_container() {
|
||||
start = con.child_by_name(part, activation.is_case_sensitive());
|
||||
|
@ -205,17 +206,16 @@ impl<'gc> MovieClipReference<'gc> {
|
|||
None => "".into(),
|
||||
// Found the reference, cached, we can't re-use `self.path` sadly, it would be quicker if we could
|
||||
// But if the clip has been re-named, since being created then `mc.path() != path`
|
||||
Some((true, _, display_object)) => AvmString::new(
|
||||
activation.context.gc_context,
|
||||
display_object.path().as_wstr(),
|
||||
),
|
||||
Some((true, _, display_object)) => {
|
||||
AvmString::new(activation.context.gc_context, display_object.path())
|
||||
}
|
||||
// Found the reference, un-cached, so our cached path must be correct
|
||||
Some((false, _, _)) => self.path.full_path,
|
||||
Some((false, _, _)) => self.0.path.full_path,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the path used for this reference
|
||||
pub fn path(&self) -> &AvmString<'gc> {
|
||||
&self.path.full_path
|
||||
&self.0.path.full_path
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue