Merge pull request #52 from Dinnerbone/feature/movie_clip_object
Movie clip access from actionscript
This commit is contained in:
commit
9c9b3db7f4
222
core/src/avm1.rs
222
core/src/avm1.rs
|
@ -1,17 +1,20 @@
|
|||
use crate::avm1::builtins::register_builtins;
|
||||
use crate::avm1::movie_clip::create_movie_clip;
|
||||
use crate::avm1::globals::create_globals;
|
||||
use crate::avm1::object::Object;
|
||||
|
||||
use crate::prelude::*;
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
use rand::{rngs::SmallRng, Rng, SeedableRng};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
|
||||
use std::io::Cursor;
|
||||
use swf::avm1::read::Reader;
|
||||
|
||||
mod builtins;
|
||||
mod movie_clip;
|
||||
mod object;
|
||||
mod globals;
|
||||
pub mod movie_clip;
|
||||
pub mod object;
|
||||
mod value;
|
||||
|
||||
pub use value::Value;
|
||||
|
||||
pub struct ActionContext<'a, 'gc, 'gc_context> {
|
||||
pub gc_context: gc_arena::MutationContext<'gc, 'gc_context>,
|
||||
|
@ -28,7 +31,7 @@ pub struct Avm1<'gc> {
|
|||
rng: SmallRng,
|
||||
constant_pool: Vec<String>,
|
||||
locals: HashMap<String, Value<'gc>>,
|
||||
globals: HashMap<String, Value<'gc>>,
|
||||
globals: GcCell<'gc, Object<'gc>>,
|
||||
}
|
||||
|
||||
unsafe impl<'gc> gc_arena::Collect for Avm1<'gc> {
|
||||
|
@ -44,15 +47,13 @@ type Error = Box<dyn std::error::Error>;
|
|||
|
||||
impl<'gc> Avm1<'gc> {
|
||||
pub fn new(gc_context: MutationContext<'gc, '_>, swf_version: u8) -> Self {
|
||||
let mut globals = HashMap::new();
|
||||
register_builtins(gc_context, &mut globals);
|
||||
Self {
|
||||
swf_version,
|
||||
stack: vec![],
|
||||
rng: SmallRng::from_seed([0u8; 16]), // TODO(Herschel): Get a proper seed on all platforms.
|
||||
constant_pool: vec![],
|
||||
locals: HashMap::new(),
|
||||
globals,
|
||||
globals: GcCell::allocate(gc_context, create_globals(gc_context)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -269,7 +270,7 @@ impl<'gc> Avm1<'gc> {
|
|||
b.push_str(&a.into_string());
|
||||
self.push(Value::String(b));
|
||||
} else {
|
||||
self.push(Value::Number(b.into_number() + a.into_number()));
|
||||
self.push(Value::Number(b.as_number() + a.as_number()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -366,7 +367,7 @@ impl<'gc> Avm1<'gc> {
|
|||
|
||||
fn action_call_method(
|
||||
&mut self,
|
||||
_context: &mut ActionContext<'_, 'gc, '_>,
|
||||
context: &mut ActionContext<'_, 'gc, '_>,
|
||||
) -> Result<(), Error> {
|
||||
let method_name = self.pop()?;
|
||||
let object = self.pop()?;
|
||||
|
@ -378,14 +379,19 @@ impl<'gc> Avm1<'gc> {
|
|||
|
||||
match method_name {
|
||||
Value::Undefined | Value::Null => {
|
||||
self.stack.push(object.call(&args)?);
|
||||
let this = context.active_clip.read().object().as_object()?.to_owned();
|
||||
self.stack.push(object.call(context, this, &args)?);
|
||||
}
|
||||
Value::String(name) => {
|
||||
if name.is_empty() {
|
||||
self.stack.push(object.call(&args)?);
|
||||
} else {
|
||||
self.stack
|
||||
.push(object.as_object()?.read().get(&name).call(&args)?);
|
||||
.push(object.call(context, object.as_object()?.to_owned(), &args)?);
|
||||
} else {
|
||||
self.stack.push(object.as_object()?.read().get(&name).call(
|
||||
context,
|
||||
object.as_object()?.to_owned(),
|
||||
&args,
|
||||
)?);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
@ -410,7 +416,7 @@ impl<'gc> Avm1<'gc> {
|
|||
}
|
||||
|
||||
fn action_decrement(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
||||
let a = self.pop()?.into_number();
|
||||
let a = self.pop()?.as_number();
|
||||
self.push(Value::Number(a - 1.0));
|
||||
Ok(())
|
||||
}
|
||||
|
@ -562,9 +568,11 @@ impl<'gc> Avm1<'gc> {
|
|||
let path = var_path.as_string()?;
|
||||
|
||||
// Special hardcoded variables
|
||||
if path == "_root" {
|
||||
// TODO: Attach this movie clip Object to an actual MovieClip. That's our root.
|
||||
self.push(create_movie_clip(context.gc_context));
|
||||
if path == "_root" || path == "this" {
|
||||
self.push(context.start_clip.read().object());
|
||||
return Ok(());
|
||||
} else if path == "_global" {
|
||||
self.push(Value::Object(self.globals));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
@ -575,17 +583,16 @@ impl<'gc> Avm1<'gc> {
|
|||
Self::resolve_slash_path_variable(context.active_clip, context.root, path)
|
||||
{
|
||||
if let Some(clip) = node.read().as_movie_clip() {
|
||||
if clip.has_variable(var_name) {
|
||||
result = Some(clip.get_variable(var_name));
|
||||
let object = clip.object().as_object()?;
|
||||
if object.read().has_property(var_name) {
|
||||
result = Some(object.read().get(var_name));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if result.is_none() {
|
||||
if let Some(value) = self.globals.get(path) {
|
||||
result = Some(value.clone());
|
||||
}
|
||||
if result.is_none() && self.globals.read().has_property(path) {
|
||||
result = Some(self.globals.read().get(path));
|
||||
}
|
||||
self.push(result.unwrap_or(Value::Undefined));
|
||||
Ok(())
|
||||
|
@ -684,7 +691,7 @@ impl<'gc> Avm1<'gc> {
|
|||
}
|
||||
|
||||
fn action_increment(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
||||
let a = self.pop()?.into_number();
|
||||
let a = self.pop()?.as_number();
|
||||
self.push(Value::Number(a + 1.0));
|
||||
Ok(())
|
||||
}
|
||||
|
@ -740,7 +747,7 @@ impl<'gc> Avm1<'gc> {
|
|||
|
||||
let result = match (a, b) {
|
||||
(Value::String(a), Value::String(b)) => b.to_string().bytes().lt(a.to_string().bytes()),
|
||||
(a, b) => b.into_number() < a.into_number(),
|
||||
(a, b) => b.as_number() < a.as_number(),
|
||||
};
|
||||
|
||||
self.push(Value::Bool(result));
|
||||
|
@ -977,7 +984,10 @@ impl<'gc> Avm1<'gc> {
|
|||
Self::resolve_slash_path_variable(context.active_clip, context.root, var_path)
|
||||
{
|
||||
if let Some(clip) = node.write(context.gc_context).as_movie_clip_mut() {
|
||||
clip.set_variable(var_name, value);
|
||||
clip.object()
|
||||
.as_object()?
|
||||
.write(context.gc_context)
|
||||
.set(var_name, value);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -1133,7 +1143,7 @@ impl<'gc> Avm1<'gc> {
|
|||
|
||||
fn action_to_number(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
||||
let val = self.pop()?;
|
||||
self.push(Value::Number(val.into_number()));
|
||||
self.push(Value::Number(val.as_number()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1150,16 +1160,8 @@ impl<'gc> Avm1<'gc> {
|
|||
}
|
||||
|
||||
fn action_type_of(&mut self, _context: &mut ActionContext) -> Result<(), Error> {
|
||||
let _type_str = match self.pop()? {
|
||||
Value::Undefined => "undefined",
|
||||
Value::Null => "null",
|
||||
Value::Number(_) => "number",
|
||||
Value::Bool(_) => "boolean",
|
||||
Value::String(_) => "string",
|
||||
Value::Object(_) => "object",
|
||||
Value::NativeFunction(_) => "function",
|
||||
};
|
||||
// TODO(Herschel): movieclip
|
||||
let type_of = self.pop()?.type_of();
|
||||
self.push(type_of);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1206,143 +1208,3 @@ impl<'gc> Avm1<'gc> {
|
|||
Err("Unimplemented action: With".into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Value<'gc> {
|
||||
Undefined,
|
||||
Null,
|
||||
Bool(bool),
|
||||
Number(f64),
|
||||
String(String),
|
||||
Object(GcCell<'gc, Object<'gc>>),
|
||||
NativeFunction(fn(&[Value<'gc>]) -> Value<'gc>),
|
||||
}
|
||||
|
||||
unsafe impl<'gc> gc_arena::Collect for Value<'gc> {
|
||||
fn trace(&self, cc: gc_arena::CollectionContext) {
|
||||
if let Value::Object(object) = self {
|
||||
object.trace(cc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Value<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Value::Undefined => f.write_str("Undefined"),
|
||||
Value::Null => f.write_str("Null"),
|
||||
Value::Bool(value) => f.debug_tuple("Bool").field(value).finish(),
|
||||
Value::Number(value) => f.debug_tuple("Number").field(value).finish(),
|
||||
Value::String(value) => f.debug_tuple("String").field(value).finish(),
|
||||
Value::Object(value) => f.debug_tuple("Object").field(value).finish(),
|
||||
Value::NativeFunction(_) => f.write_str("NativeFunction"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> Value<'gc> {
|
||||
fn into_number_v1(self) -> f64 {
|
||||
match self {
|
||||
Value::Bool(true) => 1.0,
|
||||
Value::Number(v) => v,
|
||||
Value::String(v) => v.parse().unwrap_or(0.0),
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_number(self) -> f64 {
|
||||
// ECMA-262 2nd edtion s. 9.3 ToNumber
|
||||
use std::f64::NAN;
|
||||
match self {
|
||||
Value::Undefined => NAN,
|
||||
Value::Null => NAN,
|
||||
Value::Bool(false) => 0.0,
|
||||
Value::Bool(true) => 1.0,
|
||||
Value::Number(v) => v,
|
||||
Value::String(v) => v.parse().unwrap_or(NAN), // TODO(Herschel): Handle Infinity/etc.?
|
||||
Value::Object(_object) => {
|
||||
log::error!("Unimplemented: Object ToNumber");
|
||||
0.0
|
||||
}
|
||||
Value::NativeFunction(_fn) => {
|
||||
log::error!("Unimplemented: NativeFunction ToNumber");
|
||||
0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn from_bool_v1(value: bool, swf_version: u8) -> Value<'gc> {
|
||||
// SWF version 4 did not have true bools and will push bools as 0 or 1.
|
||||
// e.g. SWF19 p. 72:
|
||||
// "If the numbers are equal, true is pushed to the stack for SWF 5 and later. For SWF 4, 1 is pushed to the stack."
|
||||
if swf_version >= 5 {
|
||||
Value::Bool(value)
|
||||
} else {
|
||||
Value::Number(if value { 1.0 } else { 0.0 })
|
||||
}
|
||||
}
|
||||
|
||||
fn into_string(self) -> String {
|
||||
match self {
|
||||
Value::Undefined => "undefined".to_string(),
|
||||
Value::Null => "null".to_string(),
|
||||
Value::Bool(v) => v.to_string(),
|
||||
Value::Number(v) => v.to_string(), // TODO(Herschel): Rounding for int?
|
||||
Value::String(v) => v,
|
||||
Value::Object(_) => "[Object object]".to_string(), // TODO(Herschel):
|
||||
Value::NativeFunction(_) => "[type Function]".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_bool(&self) -> bool {
|
||||
match *self {
|
||||
Value::Bool(v) => v,
|
||||
Value::Number(v) => v != 0.0,
|
||||
// TODO(Herschel): Value::String(v) => ??
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_i32(&self) -> Result<i32, Error> {
|
||||
self.as_f64().map(|n| n as i32)
|
||||
}
|
||||
|
||||
fn as_u32(&self) -> Result<u32, Error> {
|
||||
self.as_f64().map(|n| n as u32)
|
||||
}
|
||||
|
||||
fn as_i64(&self) -> Result<i64, Error> {
|
||||
self.as_f64().map(|n| n as i64)
|
||||
}
|
||||
|
||||
pub fn as_f64(&self) -> Result<f64, Error> {
|
||||
match *self {
|
||||
Value::Number(v) => Ok(v),
|
||||
_ => Err(format!("Expected Number, found {:?}", self).into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_string(&self) -> Result<&String, Error> {
|
||||
match self {
|
||||
Value::String(s) => Ok(s),
|
||||
_ => Err(format!("Expected String, found {:?}", self).into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_object(&self) -> Result<&GcCell<'gc, Object<'gc>>, Error> {
|
||||
if let Value::Object(object) = self {
|
||||
Ok(object)
|
||||
} else {
|
||||
Err(format!("Expected Object, found {:?}", self).into())
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&self, args: &[Value<'gc>]) -> Result<Value<'gc>, Error> {
|
||||
if let Value::NativeFunction(function) = self {
|
||||
Ok(function(args))
|
||||
} else {
|
||||
Err(format!("Expected function, found {:?}", self).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
use crate::avm1::Value;
|
||||
use gc_arena::MutationContext;
|
||||
use std::collections::HashMap;
|
||||
|
||||
mod math;
|
||||
|
||||
pub fn register_builtins<'gc>(
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
globals: &mut HashMap<String, Value<'gc>>,
|
||||
) {
|
||||
globals.insert("Math".to_string(), math::create(gc_context));
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
use crate::avm1::{Object, Value};
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
|
||||
fn abs<'gc>(args: &[Value<'gc>]) -> Value<'gc> {
|
||||
let input = args.get(0).unwrap().as_f64().unwrap();
|
||||
Value::Number(input.abs())
|
||||
}
|
||||
|
||||
fn round<'gc>(args: &[Value<'gc>]) -> Value<'gc> {
|
||||
let input = args.get(0).unwrap().as_f64().unwrap();
|
||||
Value::Number(input.round())
|
||||
}
|
||||
|
||||
pub fn create<'gc>(gc_context: MutationContext<'gc, '_>) -> Value<'gc> {
|
||||
let mut math = Object::new();
|
||||
|
||||
math.set("abs", Value::NativeFunction(abs));
|
||||
math.set("round", Value::NativeFunction(round));
|
||||
|
||||
Value::Object(GcCell::allocate(gc_context, math))
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
use crate::avm1::object::Object;
|
||||
use gc_arena::MutationContext;
|
||||
|
||||
mod math;
|
||||
|
||||
pub fn create_globals<'gc>(gc_context: MutationContext<'gc, '_>) -> Object<'gc> {
|
||||
let mut globals = Object::object(gc_context);
|
||||
|
||||
globals.set_object("Math", math::create(gc_context));
|
||||
|
||||
globals
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
use crate::avm1::{ActionContext, Object, Value};
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
use std::f64::NAN;
|
||||
|
||||
macro_rules! wrap_std {
|
||||
( $object: ident, $gc_context: ident, $($name:expr => $std:path),* ) => {{
|
||||
$(
|
||||
$object.set_function(
|
||||
$name,
|
||||
|_context, _this, args| -> Value<'gc> {
|
||||
if let Some(input) = args.get(0) {
|
||||
Value::Number($std(input.as_number()))
|
||||
} else {
|
||||
Value::Number(NAN)
|
||||
}
|
||||
},
|
||||
$gc_context,
|
||||
);
|
||||
)*
|
||||
}};
|
||||
}
|
||||
|
||||
fn atan2<'gc>(
|
||||
_context: &mut ActionContext<'_, 'gc, '_>,
|
||||
_this: GcCell<'gc, Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Value<'gc> {
|
||||
if let Some(y) = args.get(0) {
|
||||
if let Some(x) = args.get(1) {
|
||||
return Value::Number(y.as_number().atan2(x.as_number()));
|
||||
} else {
|
||||
return Value::Number(y.as_number().atan2(0.0));
|
||||
}
|
||||
}
|
||||
Value::Number(NAN)
|
||||
}
|
||||
|
||||
pub fn create<'gc>(gc_context: MutationContext<'gc, '_>) -> GcCell<'gc, Object<'gc>> {
|
||||
let mut math = Object::object(gc_context);
|
||||
|
||||
math.set("E", Value::Number(std::f64::consts::E));
|
||||
math.set("LN10", Value::Number(std::f64::consts::LN_10));
|
||||
math.set("LN2", Value::Number(std::f64::consts::LN_2));
|
||||
math.set("LOG10E", Value::Number(std::f64::consts::LOG10_E));
|
||||
math.set("LOG2E", Value::Number(std::f64::consts::LOG2_E));
|
||||
math.set("PI", Value::Number(std::f64::consts::PI));
|
||||
math.set("SQRT1_2", Value::Number(std::f64::consts::FRAC_1_SQRT_2));
|
||||
math.set("SQRT2", Value::Number(std::f64::consts::SQRT_2));
|
||||
|
||||
wrap_std!(math, gc_context,
|
||||
"abs" => f64::abs,
|
||||
"acos" => f64::acos,
|
||||
"asin" => f64::asin,
|
||||
"atan" => f64::atan,
|
||||
"ceil" => f64::ceil,
|
||||
"cos" => f64::cos,
|
||||
"exp" => f64::exp,
|
||||
"floor" => f64::floor,
|
||||
"round" => f64::round,
|
||||
"sin" => f64::sin,
|
||||
"sqrt" => f64::sqrt,
|
||||
"tan" => f64::tan
|
||||
);
|
||||
|
||||
math.set_function("atan2", atan2, gc_context);
|
||||
|
||||
GcCell::allocate(gc_context, math)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unreadable_literal)]
|
||||
#[allow(clippy::approx_constant)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::avm1::Error;
|
||||
use crate::backend::audio::NullAudioBackend;
|
||||
use crate::display_object::DisplayObject;
|
||||
use crate::movie_clip::MovieClip;
|
||||
use gc_arena::rootless_arena;
|
||||
|
||||
macro_rules! test_std {
|
||||
( $test: ident, $name: expr, $($args: expr => $out: expr),* ) => {
|
||||
#[test]
|
||||
fn $test() -> Result<(), Error> {
|
||||
with_avm(|context| {
|
||||
let math = create(context.gc_context);
|
||||
let function = math.read().get($name);
|
||||
|
||||
$(
|
||||
assert_eq!(function.call(context, math, $args)?, $out);
|
||||
)*
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn with_avm<F, R>(test: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut ActionContext) -> R,
|
||||
{
|
||||
rootless_arena(|gc_context| {
|
||||
let movie_clip: Box<dyn DisplayObject> = Box::new(MovieClip::new(gc_context));
|
||||
let root = GcCell::allocate(gc_context, movie_clip);
|
||||
let mut context = ActionContext {
|
||||
gc_context,
|
||||
global_time: 0,
|
||||
root,
|
||||
start_clip: root,
|
||||
active_clip: root,
|
||||
audio: &mut NullAudioBackend::new(),
|
||||
};
|
||||
test(&mut context)
|
||||
})
|
||||
}
|
||||
|
||||
test_std!(test_abs, "abs",
|
||||
&[] => Value::Number(NAN),
|
||||
&[Value::Null] => Value::Number(NAN),
|
||||
&[Value::Number(-50.0)] => Value::Number(50.0),
|
||||
&[Value::Number(25.0)] => Value::Number(25.0)
|
||||
);
|
||||
|
||||
test_std!(test_acos, "acos",
|
||||
&[] => Value::Number(NAN),
|
||||
&[Value::Null] => Value::Number(NAN),
|
||||
&[Value::Number(-1.0)] => Value::Number(3.141592653589793),
|
||||
&[Value::Number(0.0)] => Value::Number(1.5707963267948966),
|
||||
&[Value::Number(1.0)] => Value::Number(0.0)
|
||||
);
|
||||
|
||||
test_std!(test_asin, "asin",
|
||||
&[] => Value::Number(NAN),
|
||||
&[Value::Null] => Value::Number(NAN),
|
||||
&[Value::Number(-1.0)] => Value::Number(-1.5707963267948966),
|
||||
&[Value::Number(0.0)] => Value::Number(0.0),
|
||||
&[Value::Number(1.0)] => Value::Number(1.5707963267948966)
|
||||
);
|
||||
|
||||
test_std!(test_atan, "atan",
|
||||
&[] => Value::Number(NAN),
|
||||
&[Value::Null] => Value::Number(NAN),
|
||||
&[Value::Number(-1.0)] => Value::Number(-0.7853981633974483),
|
||||
&[Value::Number(0.0)] => Value::Number(0.0),
|
||||
&[Value::Number(1.0)] => Value::Number(0.7853981633974483)
|
||||
);
|
||||
|
||||
test_std!(test_ceil, "ceil",
|
||||
&[] => Value::Number(NAN),
|
||||
&[Value::Null] => Value::Number(NAN),
|
||||
&[Value::Number(12.5)] => Value::Number(13.0)
|
||||
);
|
||||
|
||||
test_std!(test_cos, "cos",
|
||||
&[] => Value::Number(NAN),
|
||||
&[Value::Null] => Value::Number(NAN),
|
||||
&[Value::Number(0.0)] => Value::Number(1.0),
|
||||
&[Value::Number(std::f64::consts::PI)] => Value::Number(-1.0)
|
||||
);
|
||||
|
||||
test_std!(test_exp, "exp",
|
||||
&[] => Value::Number(NAN),
|
||||
&[Value::Null] => Value::Number(NAN),
|
||||
&[Value::Number(1.0)] => Value::Number(2.718281828459045),
|
||||
&[Value::Number(2.0)] => Value::Number(7.38905609893065)
|
||||
);
|
||||
|
||||
test_std!(test_floor, "floor",
|
||||
&[] => Value::Number(NAN),
|
||||
&[Value::Null] => Value::Number(NAN),
|
||||
&[Value::Number(12.5)] => Value::Number(12.0)
|
||||
);
|
||||
|
||||
test_std!(test_round, "round",
|
||||
&[] => Value::Number(NAN),
|
||||
&[Value::Null] => Value::Number(NAN),
|
||||
&[Value::Number(12.5)] => Value::Number(13.0),
|
||||
&[Value::Number(23.2)] => Value::Number(23.0)
|
||||
);
|
||||
|
||||
test_std!(test_sin, "sin",
|
||||
&[] => Value::Number(NAN),
|
||||
&[Value::Null] => Value::Number(NAN),
|
||||
&[Value::Number(0.0)] => Value::Number(0.0),
|
||||
&[Value::Number(std::f64::consts::PI / 2.0)] => Value::Number(1.0)
|
||||
);
|
||||
|
||||
test_std!(test_sqrt, "sqrt",
|
||||
&[] => Value::Number(NAN),
|
||||
&[Value::Null] => Value::Number(NAN),
|
||||
&[Value::Number(0.0)] => Value::Number(0.0),
|
||||
&[Value::Number(5.0)] => Value::Number(2.23606797749979)
|
||||
);
|
||||
|
||||
test_std!(test_tan, "tan",
|
||||
&[] => Value::Number(NAN),
|
||||
&[Value::Null] => Value::Number(NAN),
|
||||
&[Value::Number(0.0)] => Value::Number(0.0),
|
||||
&[Value::Number(1.0)] => Value::Number(1.5574077246549023)
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn test_atan2_nan() {
|
||||
with_avm(|context| {
|
||||
let math = GcCell::allocate(context.gc_context, create(context.gc_context));
|
||||
assert_eq!(atan2(context, *math.read(), &[]), Value::Number(NAN));
|
||||
assert_eq!(
|
||||
atan2(context, *math.read(), &[Value::Number(1.0), Value::Null]),
|
||||
Value::Number(NAN)
|
||||
);
|
||||
assert_eq!(
|
||||
atan2(
|
||||
context,
|
||||
*math.read(),
|
||||
&[Value::Number(1.0), Value::Undefined]
|
||||
),
|
||||
Value::Number(NAN)
|
||||
);
|
||||
assert_eq!(
|
||||
atan2(
|
||||
context,
|
||||
*math.read(),
|
||||
&[Value::Undefined, Value::Number(1.0)]
|
||||
),
|
||||
Value::Number(NAN)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_atan2_valid() {
|
||||
with_avm(|context| {
|
||||
let math = GcCell::allocate(context.gc_context, create(context.gc_context));
|
||||
assert_eq!(
|
||||
atan2(context, *math.read(), &[Value::Number(10.0)]),
|
||||
Value::Number(std::f64::consts::FRAC_PI_2)
|
||||
);
|
||||
assert_eq!(
|
||||
atan2(
|
||||
context,
|
||||
*math.read(),
|
||||
&[Value::Number(1.0), Value::Number(2.0)]
|
||||
),
|
||||
Value::Number(0.4636476090008061)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,18 +1,82 @@
|
|||
use crate::avm1::{Object, Value};
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
use crate::avm1::object::Object;
|
||||
use crate::avm1::Value;
|
||||
use crate::movie_clip::MovieClip;
|
||||
use gc_arena::MutationContext;
|
||||
|
||||
pub fn create_movie_clip<'gc>(gc_context: MutationContext<'gc, '_>) -> Value<'gc> {
|
||||
let mut class = Object::new();
|
||||
|
||||
class.set(
|
||||
"getBytesTotal",
|
||||
Value::NativeFunction(|_args: &[Value<'gc>]| Value::Number(1.0)),
|
||||
);
|
||||
|
||||
class.set(
|
||||
"getBytesLoaded",
|
||||
Value::NativeFunction(|_args: &[Value<'gc>]| Value::Number(1.0)),
|
||||
);
|
||||
|
||||
Value::Object(GcCell::allocate(gc_context, class))
|
||||
macro_rules! with_movie_clip {
|
||||
( $gc_context: ident, $object:ident, $($name:expr => $fn:expr),* ) => {{
|
||||
$(
|
||||
$object.set_function(
|
||||
$name,
|
||||
|_context, this, args| -> Value<'gc> {
|
||||
if let Some(display_object) = this.read().display_node() {
|
||||
if let Some(movie_clip) = display_object.read().as_movie_clip() {
|
||||
return $fn(movie_clip, args);
|
||||
}
|
||||
}
|
||||
Value::Undefined
|
||||
},
|
||||
$gc_context,
|
||||
);
|
||||
)*
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! with_movie_clip_mut {
|
||||
( $gc_context: ident, $object:ident, $($name:expr => $fn:expr),* ) => {{
|
||||
$(
|
||||
$object.set_function(
|
||||
$name,
|
||||
|context, this, args| -> Value<'gc> {
|
||||
if let Some(display_object) = this.read().display_node() {
|
||||
if let Some(movie_clip) = display_object.write(context.gc_context).as_movie_clip_mut() {
|
||||
return $fn(movie_clip, args);
|
||||
}
|
||||
}
|
||||
Value::Undefined
|
||||
},
|
||||
$gc_context,
|
||||
);
|
||||
)*
|
||||
}};
|
||||
}
|
||||
|
||||
pub fn create_movie_object<'gc>(gc_context: MutationContext<'gc, '_>) -> Object<'gc> {
|
||||
let mut object = Object::object(gc_context);
|
||||
|
||||
with_movie_clip_mut!(
|
||||
gc_context,
|
||||
object,
|
||||
"nextFrame" => |movie_clip: &mut MovieClip, _args| {
|
||||
movie_clip.next_frame();
|
||||
Value::Undefined
|
||||
},
|
||||
"prevFrame" => |movie_clip: &mut MovieClip, _args| {
|
||||
movie_clip.prev_frame();
|
||||
Value::Undefined
|
||||
},
|
||||
"play" => |movie_clip: &mut MovieClip, _args| {
|
||||
movie_clip.play();
|
||||
Value::Undefined
|
||||
},
|
||||
"stop" => |movie_clip: &mut MovieClip, _args| {
|
||||
movie_clip.stop();
|
||||
Value::Undefined
|
||||
}
|
||||
);
|
||||
|
||||
with_movie_clip!(
|
||||
gc_context,
|
||||
object,
|
||||
"getBytesLoaded" => |_movie_clip: &MovieClip, _args| {
|
||||
// TODO find a correct value
|
||||
Value::Number(0.0)
|
||||
},
|
||||
"getBytesTotal" => |_movie_clip: &MovieClip, _args| {
|
||||
// TODO find a correct value
|
||||
Value::Number(0.0)
|
||||
}
|
||||
);
|
||||
|
||||
object
|
||||
}
|
||||
|
|
|
@ -1,27 +1,141 @@
|
|||
use crate::avm1::Value;
|
||||
use gc_arena::Collect;
|
||||
use crate::avm1::{ActionContext, Value};
|
||||
use crate::display_object::DisplayNode;
|
||||
use core::fmt;
|
||||
use gc_arena::{GcCell, MutationContext};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone, Debug, Collect, Default)]
|
||||
#[collect(empty_drop)]
|
||||
pub type NativeFunction<'gc> =
|
||||
fn(&mut ActionContext<'_, 'gc, '_>, GcCell<'gc, Object<'gc>>, &[Value<'gc>]) -> Value<'gc>;
|
||||
|
||||
pub const TYPE_OF_OBJECT: &str = "object";
|
||||
pub const TYPE_OF_FUNCTION: &str = "function";
|
||||
pub const TYPE_OF_MOVIE_CLIP: &str = "movieclip";
|
||||
|
||||
fn default_to_string<'gc>(
|
||||
_: &mut ActionContext<'_, 'gc, '_>,
|
||||
_: GcCell<'gc, Object<'gc>>,
|
||||
_: &[Value<'gc>],
|
||||
) -> Value<'gc> {
|
||||
Value::String("[Object object]".to_string())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Object<'gc> {
|
||||
display_node: Option<DisplayNode<'gc>>,
|
||||
values: HashMap<String, Value<'gc>>,
|
||||
function: Option<NativeFunction<'gc>>,
|
||||
type_of: &'static str,
|
||||
}
|
||||
|
||||
unsafe impl<'gc> gc_arena::Collect for Object<'gc> {
|
||||
fn trace(&self, cc: gc_arena::CollectionContext) {
|
||||
self.display_node.trace(cc);
|
||||
self.values.trace(cc);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Object<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Object")
|
||||
.field("display_node", &self.display_node)
|
||||
.field("values", &self.values)
|
||||
.field("function", &self.function.is_some())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> Object<'gc> {
|
||||
pub fn new() -> Self {
|
||||
pub fn object(gc_context: MutationContext<'gc, '_>) -> Self {
|
||||
let mut result = Self {
|
||||
type_of: TYPE_OF_OBJECT,
|
||||
display_node: None,
|
||||
values: HashMap::new(),
|
||||
function: None,
|
||||
};
|
||||
|
||||
result.set_function("toString", default_to_string, gc_context);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn function(function: NativeFunction<'gc>) -> Self {
|
||||
Self {
|
||||
type_of: TYPE_OF_FUNCTION,
|
||||
function: Some(function),
|
||||
display_node: None,
|
||||
values: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Value<'gc> {
|
||||
self.values
|
||||
.get(name)
|
||||
.map_or(Value::Undefined, |v| v.to_owned())
|
||||
pub fn set_display_node(&mut self, display_node: DisplayNode<'gc>) {
|
||||
self.display_node = Some(display_node);
|
||||
}
|
||||
|
||||
pub fn display_node(&self) -> Option<DisplayNode<'gc>> {
|
||||
self.display_node
|
||||
}
|
||||
|
||||
pub fn set(&mut self, name: &str, value: Value<'gc>) {
|
||||
self.values.insert(name.to_owned(), value);
|
||||
}
|
||||
|
||||
pub fn set_object(&mut self, name: &str, object: GcCell<'gc, Object<'gc>>) {
|
||||
self.values.insert(name.to_owned(), Value::Object(object));
|
||||
}
|
||||
|
||||
pub fn set_function(
|
||||
&mut self,
|
||||
name: &str,
|
||||
function: NativeFunction<'gc>,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
) {
|
||||
self.set(
|
||||
name,
|
||||
Value::Object(GcCell::allocate(gc_context, Object::function(function))),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Value<'gc> {
|
||||
if let Some(value) = self.values.get(name) {
|
||||
return value.to_owned();
|
||||
}
|
||||
Value::Undefined
|
||||
}
|
||||
|
||||
pub fn has_property(&self, name: &str) -> bool {
|
||||
self.values.contains_key(name)
|
||||
}
|
||||
|
||||
pub fn has_own_property(&self, name: &str) -> bool {
|
||||
self.values.contains_key(name)
|
||||
}
|
||||
|
||||
pub fn call(
|
||||
&self,
|
||||
context: &mut ActionContext<'_, 'gc, '_>,
|
||||
this: GcCell<'gc, Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Value<'gc> {
|
||||
if let Some(function) = self.function {
|
||||
function(context, this, args)
|
||||
} else {
|
||||
Value::Undefined
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_string(&self) -> String {
|
||||
if self.function.is_some() {
|
||||
"[type Function]".to_string()
|
||||
} else {
|
||||
"[object Object]".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_type_of(&mut self, type_of: &'static str) {
|
||||
self.type_of = type_of;
|
||||
}
|
||||
|
||||
pub fn type_of(&self) -> &'static str {
|
||||
self.type_of
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
use crate::avm1::object::Object;
|
||||
use crate::avm1::{ActionContext, Error};
|
||||
use gc_arena::GcCell;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Value<'gc> {
|
||||
Undefined,
|
||||
Null,
|
||||
Bool(bool),
|
||||
Number(f64),
|
||||
String(String),
|
||||
Object(GcCell<'gc, Object<'gc>>),
|
||||
}
|
||||
|
||||
unsafe impl<'gc> gc_arena::Collect for Value<'gc> {
|
||||
fn trace(&self, cc: gc_arena::CollectionContext) {
|
||||
if let Value::Object(object) = self {
|
||||
object.trace(cc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Value<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match self {
|
||||
Value::Undefined => match other {
|
||||
Value::Undefined => true,
|
||||
_ => false,
|
||||
},
|
||||
Value::Null => match other {
|
||||
Value::Null => true,
|
||||
_ => false,
|
||||
},
|
||||
Value::Bool(value) => match other {
|
||||
Value::Bool(other_value) => value == other_value,
|
||||
_ => false,
|
||||
},
|
||||
Value::Number(value) => match other {
|
||||
Value::Number(other_value) => {
|
||||
(value == other_value) || (value.is_nan() && other_value.is_nan())
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
Value::String(value) => match other {
|
||||
Value::String(other_value) => value == other_value,
|
||||
_ => false,
|
||||
},
|
||||
Value::Object(value) => match other {
|
||||
Value::Object(other_value) => value.as_ptr() == other_value.as_ptr(),
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> Value<'gc> {
|
||||
pub fn into_number_v1(self) -> f64 {
|
||||
match self {
|
||||
Value::Bool(true) => 1.0,
|
||||
Value::Number(v) => v,
|
||||
Value::String(v) => v.parse().unwrap_or(0.0),
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_number(&self) -> f64 {
|
||||
// ECMA-262 2nd edtion s. 9.3 ToNumber
|
||||
use std::f64::NAN;
|
||||
match self {
|
||||
Value::Undefined => NAN,
|
||||
Value::Null => NAN,
|
||||
Value::Bool(false) => 0.0,
|
||||
Value::Bool(true) => 1.0,
|
||||
Value::Number(v) => *v,
|
||||
Value::String(v) => v.parse().unwrap_or(NAN), // TODO(Herschel): Handle Infinity/etc.?
|
||||
Value::Object(_object) => {
|
||||
log::error!("Unimplemented: Object ToNumber");
|
||||
0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_bool_v1(value: bool, swf_version: u8) -> Value<'gc> {
|
||||
// SWF version 4 did not have true bools and will push bools as 0 or 1.
|
||||
// e.g. SWF19 p. 72:
|
||||
// "If the numbers are equal, true is pushed to the stack for SWF 5 and later. For SWF 4, 1 is pushed to the stack."
|
||||
if swf_version >= 5 {
|
||||
Value::Bool(value)
|
||||
} else {
|
||||
Value::Number(if value { 1.0 } else { 0.0 })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_string(self) -> String {
|
||||
match self {
|
||||
Value::Undefined => "undefined".to_string(),
|
||||
Value::Null => "null".to_string(),
|
||||
Value::Bool(v) => v.to_string(),
|
||||
Value::Number(v) => v.to_string(), // TODO(Herschel): Rounding for int?
|
||||
Value::String(v) => v,
|
||||
Value::Object(object) => object.read().as_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_bool(&self) -> bool {
|
||||
match *self {
|
||||
Value::Bool(v) => v,
|
||||
Value::Number(v) => v != 0.0,
|
||||
// TODO(Herschel): Value::String(v) => ??
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_of(&self) -> Value<'gc> {
|
||||
Value::String(
|
||||
match self {
|
||||
Value::Undefined => "undefined",
|
||||
Value::Null => "null",
|
||||
Value::Number(_) => "number",
|
||||
Value::Bool(_) => "boolean",
|
||||
Value::String(_) => "string",
|
||||
Value::Object(object) => object.read().type_of(),
|
||||
}
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn as_i32(&self) -> Result<i32, Error> {
|
||||
self.as_f64().map(|n| n as i32)
|
||||
}
|
||||
|
||||
pub fn as_u32(&self) -> Result<u32, Error> {
|
||||
self.as_f64().map(|n| n as u32)
|
||||
}
|
||||
|
||||
pub fn as_i64(&self) -> Result<i64, Error> {
|
||||
self.as_f64().map(|n| n as i64)
|
||||
}
|
||||
|
||||
pub fn as_f64(&self) -> Result<f64, Error> {
|
||||
match *self {
|
||||
Value::Number(v) => Ok(v),
|
||||
_ => Err(format!("Expected Number, found {:?}", self).into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_string(&self) -> Result<&String, Error> {
|
||||
match self {
|
||||
Value::String(s) => Ok(s),
|
||||
_ => Err(format!("Expected String, found {:?}", self).into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_object(&self) -> Result<GcCell<'gc, Object<'gc>>, Error> {
|
||||
if let Value::Object(object) = self {
|
||||
Ok(object.to_owned())
|
||||
} else {
|
||||
Err(format!("Expected Object, found {:?}", self).into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call(
|
||||
&self,
|
||||
context: &mut ActionContext<'_, 'gc, '_>,
|
||||
this: GcCell<'gc, Object<'gc>>,
|
||||
args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, Error> {
|
||||
if let Value::Object(object) = self {
|
||||
Ok(object.read().call(context, this, args))
|
||||
} else {
|
||||
Err(format!("Expected function, found {:?}", self).into())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ use crate::player::{RenderContext, UpdateContext};
|
|||
use crate::prelude::*;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Button<'gc> {
|
||||
base: DisplayObjectBase<'gc>,
|
||||
static_data: gc_arena::Gc<'gc, ButtonStatic>,
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use crate::avm1::Value;
|
||||
use crate::player::{RenderContext, UpdateContext};
|
||||
use crate::prelude::*;
|
||||
use crate::transform::Transform;
|
||||
use gc_arena::{Collect, GcCell};
|
||||
use gc_arena::{Collect, GcCell, MutationContext};
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(Clone, Collect)]
|
||||
#[derive(Clone, Collect, Debug)]
|
||||
#[collect(empty_drop)]
|
||||
pub struct DisplayObjectBase<'gc> {
|
||||
parent: Option<DisplayNode<'gc>>,
|
||||
|
@ -68,7 +70,7 @@ impl<'gc> DisplayObject<'gc> for DisplayObjectBase<'gc> {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait DisplayObject<'gc>: 'gc + Collect {
|
||||
pub trait DisplayObject<'gc>: 'gc + Collect + Debug {
|
||||
fn local_bounds(&self) -> BoundingBox {
|
||||
BoundingBox::default()
|
||||
}
|
||||
|
@ -115,6 +117,10 @@ pub trait DisplayObject<'gc>: 'gc + Collect {
|
|||
}
|
||||
fn box_clone(&self) -> Box<dyn DisplayObject<'gc>>;
|
||||
|
||||
fn object(&self) -> Value<'gc> {
|
||||
Value::Undefined // todo: impl for every type and delete this fallback
|
||||
}
|
||||
|
||||
fn hit_test(&self, _: (Twips, Twips)) -> bool {
|
||||
false
|
||||
}
|
||||
|
@ -126,6 +132,13 @@ pub trait DisplayObject<'gc>: 'gc + Collect {
|
|||
) -> Option<DisplayNode<'gc>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn post_instantiation(
|
||||
&mut self,
|
||||
_gc_context: MutationContext<'gc, '_>,
|
||||
_display_object: DisplayNode<'gc>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'gc> Clone for Box<dyn DisplayObject<'gc>> {
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::display_object::{DisplayObject, DisplayObjectBase};
|
|||
use crate::player::{RenderContext, UpdateContext};
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Graphic<'gc> {
|
||||
base: DisplayObjectBase<'gc>,
|
||||
static_data: gc_arena::Gc<'gc, GraphicStatic>,
|
||||
|
|
|
@ -58,7 +58,11 @@ impl<'gc> Library<'gc> {
|
|||
return Err("Character id doesn't exist".into());
|
||||
}
|
||||
};
|
||||
Ok(GcCell::allocate(gc_context, obj))
|
||||
let result = GcCell::allocate(gc_context, obj);
|
||||
result
|
||||
.write(gc_context)
|
||||
.post_instantiation(gc_context, result);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn get_font(&self, id: CharacterId) -> Option<&Font> {
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::prelude::*;
|
|||
use std::collections::HashMap;
|
||||
use swf::Twips;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MorphShape<'gc> {
|
||||
base: DisplayObjectBase<'gc>,
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use crate::avm1;
|
||||
use crate::avm1::movie_clip::create_movie_object;
|
||||
use crate::avm1::object::{Object, TYPE_OF_MOVIE_CLIP};
|
||||
use crate::avm1::Value;
|
||||
use crate::backend::audio::AudioStreamHandle;
|
||||
use crate::character::Character;
|
||||
use crate::color_transform::ColorTransform;
|
||||
|
@ -11,14 +13,14 @@ use crate::player::{RenderContext, UpdateContext};
|
|||
use crate::prelude::*;
|
||||
use crate::tag_utils::{self, DecodeResult, SwfStream};
|
||||
use crate::text::Text;
|
||||
use gc_arena::{Gc, MutationContext};
|
||||
use gc_arena::{Gc, GcCell, MutationContext};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use swf::read::SwfRead;
|
||||
|
||||
type Depth = i16;
|
||||
type FrameNumber = u16;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MovieClip<'gc> {
|
||||
base: DisplayObjectBase<'gc>,
|
||||
static_data: Gc<'gc, MovieClipStatic>,
|
||||
|
@ -28,7 +30,7 @@ pub struct MovieClip<'gc> {
|
|||
current_frame: FrameNumber,
|
||||
audio_stream: Option<AudioStreamHandle>,
|
||||
children: BTreeMap<Depth, DisplayNode<'gc>>,
|
||||
variables: HashMap<String, avm1::Value<'gc>>,
|
||||
object: GcCell<'gc, Object<'gc>>,
|
||||
}
|
||||
|
||||
impl<'gc> MovieClip<'gc> {
|
||||
|
@ -42,7 +44,7 @@ impl<'gc> MovieClip<'gc> {
|
|||
current_frame: 0,
|
||||
audio_stream: None,
|
||||
children: BTreeMap::new(),
|
||||
variables: HashMap::new(),
|
||||
object: GcCell::allocate(gc_context, create_movie_object(gc_context)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,7 +74,7 @@ impl<'gc> MovieClip<'gc> {
|
|||
current_frame: 0,
|
||||
audio_stream: None,
|
||||
children: BTreeMap::new(),
|
||||
variables: HashMap::new(),
|
||||
object: GcCell::allocate(gc_context, create_movie_object(gc_context)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,23 +227,6 @@ impl<'gc> MovieClip<'gc> {
|
|||
self.goto_queue.clear();
|
||||
}
|
||||
|
||||
pub fn has_variable(&self, var_name: &str) -> bool {
|
||||
self.variables.contains_key(var_name)
|
||||
}
|
||||
|
||||
pub fn get_variable(&self, var_name: &str) -> avm1::Value<'gc> {
|
||||
// TODO: Value should be Copy (and contain a Cow/GcCell for big objects)
|
||||
self.variables
|
||||
.get(var_name)
|
||||
.unwrap_or(&avm1::Value::Undefined)
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub fn set_variable(&mut self, var_name: &str, value: avm1::Value<'gc>) {
|
||||
// TODO: Cow for String values.
|
||||
self.variables.insert(var_name.to_owned(), value);
|
||||
}
|
||||
|
||||
pub fn id(&self) -> CharacterId {
|
||||
self.static_data.id
|
||||
}
|
||||
|
@ -442,6 +427,20 @@ impl<'gc> DisplayObject<'gc> for MovieClip<'gc> {
|
|||
fn as_movie_clip_mut(&mut self) -> Option<&mut crate::movie_clip::MovieClip<'gc>> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
fn post_instantiation(
|
||||
&mut self,
|
||||
gc_context: MutationContext<'gc, '_>,
|
||||
display_object: DisplayNode<'gc>,
|
||||
) {
|
||||
let mut object = self.object.write(gc_context);
|
||||
object.set_display_node(display_object);
|
||||
object.set_type_of(TYPE_OF_MOVIE_CLIP);
|
||||
}
|
||||
|
||||
fn object(&self) -> Value<'gc> {
|
||||
Value::Object(self.object)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'gc> gc_arena::Collect for MovieClip<'gc> {
|
||||
|
@ -452,6 +451,7 @@ unsafe impl<'gc> gc_arena::Collect for MovieClip<'gc> {
|
|||
}
|
||||
self.base.trace(cc);
|
||||
self.static_data.trace(cc);
|
||||
self.object.trace(cc);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::player::{RenderContext, UpdateContext};
|
|||
use crate::prelude::*;
|
||||
use crate::transform::Transform;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Text<'gc> {
|
||||
base: DisplayObjectBase<'gc>,
|
||||
text_transform: Matrix,
|
||||
|
|
|
@ -4,7 +4,7 @@ use gc_arena::Collect;
|
|||
/// Represents the transform for a DisplayObject.
|
||||
/// This includes both the transformation matrix and the color transform.
|
||||
///
|
||||
#[derive(Clone, Collect)]
|
||||
#[derive(Clone, Collect, Debug)]
|
||||
#[collect(require_static)]
|
||||
pub struct Transform {
|
||||
pub matrix: Matrix,
|
||||
|
|
Loading…
Reference in New Issue