avm2: Implement ScopeChain caching
This commit is contained in:
parent
37ec94f95b
commit
e7612f571d
|
@ -134,6 +134,25 @@ impl<'gc, V> PropertyMap<'gc, V> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn insert_with_namespace(
|
||||
&mut self,
|
||||
ns: Namespace<'gc>,
|
||||
name: AvmString<'gc>,
|
||||
mut value: V,
|
||||
) -> Option<V> {
|
||||
let bucket = self.0.entry(name).or_default();
|
||||
|
||||
if let Some((_, old_value)) = bucket.iter_mut().find(|(n, _)| *n == ns) {
|
||||
swap(old_value, &mut value);
|
||||
|
||||
Some(value)
|
||||
} else {
|
||||
bucket.push((ns, value));
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn remove(&mut self, name: QName<'gc>) -> Option<V> {
|
||||
let bucket = self.0.get_mut(&name.local_name());
|
||||
|
|
|
@ -5,10 +5,11 @@ use crate::avm2::domain::Domain;
|
|||
use crate::avm2::object::{Object, TObject};
|
||||
use crate::avm2::value::Value;
|
||||
use crate::avm2::Error;
|
||||
use crate::avm2::Multiname;
|
||||
use crate::avm2::{Multiname, Namespace};
|
||||
use core::fmt;
|
||||
use gc_arena::{Collect, Gc, MutationContext};
|
||||
use std::ops::Deref;
|
||||
use gc_arena::{Collect, GcCell, MutationContext};
|
||||
|
||||
use super::property_map::PropertyMap;
|
||||
|
||||
/// Represents a Scope that can be on either a ScopeChain or local ScopeStack.
|
||||
#[derive(Collect, Clone, Copy, Debug)]
|
||||
|
@ -47,6 +48,33 @@ impl<'gc> Scope<'gc> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Internal container that a ScopeChain uses
|
||||
#[derive(Collect, Clone, Debug)]
|
||||
#[collect(no_drop)]
|
||||
struct ScopeContainer<'gc> {
|
||||
/// The scopes of this ScopeChain
|
||||
scopes: Vec<Scope<'gc>>,
|
||||
|
||||
/// The cache of this ScopeChain. A value of None indicates that caching is disabled
|
||||
/// for this ScopeChain.
|
||||
cache: Option<PropertyMap<'gc, Object<'gc>>>,
|
||||
}
|
||||
|
||||
impl<'gc> ScopeContainer<'gc> {
|
||||
fn new(scopes: Vec<Scope<'gc>>) -> Self {
|
||||
let cache = (!scopes.iter().any(|scope| scope.with)).then(PropertyMap::default);
|
||||
Self { scopes, cache }
|
||||
}
|
||||
|
||||
fn get(&self, index: usize) -> Option<Scope<'gc>> {
|
||||
self.scopes.get(index).cloned()
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.scopes.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// A ScopeChain "chains" scopes together.
|
||||
///
|
||||
/// A ScopeChain is used for "remembering" what a scope looked like. A ScopeChain also
|
||||
|
@ -63,14 +91,14 @@ impl<'gc> Scope<'gc> {
|
|||
#[derive(Collect, Clone, Copy)]
|
||||
#[collect(no_drop)]
|
||||
pub struct ScopeChain<'gc> {
|
||||
scopes: Option<Gc<'gc, Vec<Scope<'gc>>>>,
|
||||
container: Option<GcCell<'gc, ScopeContainer<'gc>>>,
|
||||
domain: Domain<'gc>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for ScopeChain<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ScopeChain")
|
||||
.field("scopes", &self.scopes)
|
||||
.field("container", &self.container)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +107,7 @@ impl<'gc> ScopeChain<'gc> {
|
|||
/// Creates a brand new ScopeChain with a domain. The domain should be the current domain in use.
|
||||
pub fn new(domain: Domain<'gc>) -> Self {
|
||||
Self {
|
||||
scopes: None,
|
||||
container: None,
|
||||
domain,
|
||||
}
|
||||
}
|
||||
|
@ -91,14 +119,14 @@ impl<'gc> ScopeChain<'gc> {
|
|||
return *self;
|
||||
}
|
||||
// TODO: This current implementation is a bit expensive, but it is exactly what avmplus does, so it's good enough for now.
|
||||
match self.scopes {
|
||||
Some(scopes) => {
|
||||
match self.container {
|
||||
Some(container) => {
|
||||
// The new ScopeChain is created by cloning the scopes of this ScopeChain,
|
||||
// and pushing the new scopes on top of that.
|
||||
let mut cloned = scopes.deref().clone();
|
||||
let mut cloned = container.read().scopes.clone();
|
||||
cloned.extend_from_slice(new_scopes);
|
||||
Self {
|
||||
scopes: Some(Gc::allocate(mc, cloned)),
|
||||
container: Some(GcCell::allocate(mc, ScopeContainer::new(cloned))),
|
||||
domain: self.domain,
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +134,10 @@ impl<'gc> ScopeChain<'gc> {
|
|||
// We are chaining on top of an empty ScopeChain, so we don't actually
|
||||
// need to chain anything.
|
||||
Self {
|
||||
scopes: Some(Gc::allocate(mc, new_scopes.to_vec())),
|
||||
container: Some(GcCell::allocate(
|
||||
mc,
|
||||
ScopeContainer::new(new_scopes.to_vec()),
|
||||
)),
|
||||
domain: self.domain,
|
||||
}
|
||||
}
|
||||
|
@ -114,11 +145,14 @@ impl<'gc> ScopeChain<'gc> {
|
|||
}
|
||||
|
||||
pub fn get(&self, index: usize) -> Option<Scope<'gc>> {
|
||||
self.scopes.and_then(|scopes| scopes.get(index).cloned())
|
||||
self.container
|
||||
.and_then(|container| container.read().get(index))
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.scopes.map(|scopes| scopes.is_empty()).unwrap_or(true)
|
||||
self.container
|
||||
.map(|container| container.read().is_empty())
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
/// Returns the domain associated with this ScopeChain.
|
||||
|
@ -126,38 +160,77 @@ impl<'gc> ScopeChain<'gc> {
|
|||
self.domain
|
||||
}
|
||||
|
||||
#[allow(clippy::collapsible_if)]
|
||||
pub fn find(
|
||||
fn find_internal(
|
||||
&self,
|
||||
multiname: &Multiname<'gc>,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
) -> Result<Option<Object<'gc>>, Error<'gc>> {
|
||||
// First search our scopes
|
||||
if let Some(scopes) = self.scopes {
|
||||
for (depth, scope) in scopes.iter().enumerate().rev() {
|
||||
let values = scope.values();
|
||||
|
||||
) -> Result<Option<(Option<Namespace<'gc>>, Object<'gc>)>, Error<'gc>> {
|
||||
if let Some(container) = self.container {
|
||||
for (depth, scope) in container.read().scopes.iter().enumerate().rev() {
|
||||
// We search the dynamic properties if either conditions are met:
|
||||
// 1. Scope is a `with` scope
|
||||
// 2. We are at depth 0 (global scope)
|
||||
//
|
||||
// But no matter what, we always search traits first.
|
||||
if values.has_trait(multiname) {
|
||||
return Ok(Some(values));
|
||||
} else if scope.with() || depth == 0 {
|
||||
if values.has_own_property(multiname) {
|
||||
return Ok(Some(values));
|
||||
|
||||
// NOTE: We are manually searching the vtable's traits so we can figure out which namespace the trait
|
||||
// belongs to.
|
||||
let values = scope.values();
|
||||
if let Some(vtable) = values.vtable() {
|
||||
if let Some((namespace, _)) = vtable.get_trait_with_ns(multiname) {
|
||||
return Ok(Some((Some(namespace), values)));
|
||||
}
|
||||
}
|
||||
|
||||
// Wasn't in the objects traits, let's try dynamic properties if the conditions are right.
|
||||
if (scope.with() || depth == 0) && values.has_own_property(multiname) {
|
||||
// NOTE: We return the QName as `None` to indicate that we should never cache this result.
|
||||
// We NEVER cache the result of dynamic properties.
|
||||
return Ok(Some((None, values)));
|
||||
}
|
||||
}
|
||||
}
|
||||
// That didn't work... let's try searching the domain now.
|
||||
if let Some((_qname, mut script)) = self.domain.get_defining_script(multiname)? {
|
||||
return Ok(Some(script.globals(&mut activation.context)?));
|
||||
if let Some((qname, mut script)) = self.domain.get_defining_script(multiname)? {
|
||||
return Ok(Some((
|
||||
Some(qname.namespace()),
|
||||
script.globals(&mut activation.context)?,
|
||||
)));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn find(
|
||||
&self,
|
||||
multiname: &Multiname<'gc>,
|
||||
activation: &mut Activation<'_, 'gc>,
|
||||
) -> Result<Option<Object<'gc>>, Error<'gc>> {
|
||||
// First we check the cache of our container
|
||||
if let Some(container) = self.container {
|
||||
if let Some(cache) = &container.read().cache {
|
||||
let cached = cache.get_for_multiname(multiname);
|
||||
if cached.is_some() {
|
||||
return Ok(cached.cloned());
|
||||
}
|
||||
}
|
||||
}
|
||||
let found = self.find_internal(multiname, activation)?;
|
||||
if let (Some((Some(ns), obj)), Some(container)) = (found, self.container) {
|
||||
// We found a value that hasn't been cached yet, so let's try to cache it now
|
||||
let mut write = container.write(activation.context.gc_context);
|
||||
if let Some(ref mut cache) = write.cache {
|
||||
cache.insert_with_namespace(
|
||||
ns,
|
||||
multiname
|
||||
.local_name()
|
||||
.expect("Resolvable multinames should always have a local name"),
|
||||
obj,
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(found.map(|o| o.1))
|
||||
}
|
||||
|
||||
pub fn resolve(
|
||||
&self,
|
||||
name: &Multiname<'gc>,
|
||||
|
|
|
@ -105,6 +105,14 @@ impl<'gc> VTable<'gc> {
|
|||
.cloned()
|
||||
}
|
||||
|
||||
pub fn get_trait_with_ns(self, name: &Multiname<'gc>) -> Option<(Namespace<'gc>, Property)> {
|
||||
self.0
|
||||
.read()
|
||||
.resolved_traits
|
||||
.get_with_ns_for_multiname(name)
|
||||
.map(|(ns, p)| (ns, *p))
|
||||
}
|
||||
|
||||
/// Coerces `value` to the type of the slot with id `slot_id`
|
||||
pub fn coerce_trait_value(
|
||||
&self,
|
||||
|
|
Loading…
Reference in New Issue