avm2: Port `Math` to ActionScript

This commit is contained in:
relrelb 2022-08-15 02:34:45 +03:00 committed by Mike Welsh
parent 4e9cc919a1
commit d152cc3e64
5 changed files with 71 additions and 85 deletions

View File

@ -1,7 +1,7 @@
//! An internal Ruffle utility to build our playerglobal //! An internal Ruffle utility to build our playerglobal
//! `library.swf` //! `library.swf`
use convert_case::{Case, Casing}; use convert_case::{Boundary, Case, Casing};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::quote; use quote::quote;
use std::fs::File; use std::fs::File;
@ -109,7 +109,13 @@ fn flash_to_rust_path(path: &str) -> String {
// so 'URLLoader' becomes 'url_loader' // so 'URLLoader' becomes 'url_loader'
let components = path let components = path
.split('.') .split('.')
.map(|component| component.to_case(Case::Snake)) .map(|component| {
component
.from_case(Case::Camel)
// Do not split on a letter followed by a digit, so e.g. `atan2` won't become `atan_2`.
.without_boundaries(&[Boundary::UpperDigit, Boundary::LowerDigit])
.to_case(Case::Snake)
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// Form a Rust path from the snake-case components // Form a Rust path from the snake-case components
components.join("::") components.join("::")

View File

@ -432,7 +432,6 @@ pub fn load_player_globals<'gc>(
object_class, object_class,
)?; )?;
class(activation, math::create_class(mc), script)?;
class(activation, json::create_class(mc), script)?; class(activation, json::create_class(mc), script)?;
avm2_system_class!(regexp, activation, regexp::create_class(mc), script); avm2_system_class!(regexp, activation, regexp::create_class(mc), script);
avm2_system_class!(vector, activation, vector::create_class(mc), script); avm2_system_class!(vector, activation, vector::create_class(mc), script);

View File

@ -0,0 +1,38 @@
package {
public final class Math {
public static const E: Number = 2.718281828459045;
public static const LN10: Number = 2.302585092994046;
public static const LN2: Number = 0.6931471805599453;
public static const LOG10E: Number = 0.4342944819032518;
public static const LOG2E: Number = 1.442695040888963387;
public static const PI: Number = 3.141592653589793;
public static const SQRT1_2: Number = 0.7071067811865476;
public static const SQRT2: Number = 1.4142135623730951;
public static native function abs(x: Number): Number;
public static native function acos(x: Number): Number;
public static native function asin(x: Number): Number;
public static native function atan(x: Number): Number;
public static native function ceil(x: Number): Number;
public static native function cos(x: Number): Number;
public static native function exp(x: Number): Number;
public static native function floor(x: Number): Number;
public static native function log(x: Number): Number;
public static native function round(x: Number): Number;
public static native function sin(x: Number): Number;
public static native function sqrt(x: Number): Number;
public static native function tan(x: Number): Number;
public static native function atan2(y: Number, x: Number): Number;
public static native function pow(x: Number, y: Number): Number;
// This is a hacky way to specify `-Infinity` as a default value.
private static const NegInfinity: Number = -1 / 0;
public static native function max(x: Number = NegInfinity, y: Number = NegInfinity, ...rest): Number;
// TODO: Remove this once `Infinity` is properly defined in ActionScript.
private static const Infinity: Number = 1 / 0;
public static native function min(x: Number = Infinity, y: Number = Infinity, ...rest): Number;
public static native function random(): Number;
}
}

View File

@ -2,6 +2,7 @@
// need to come before subclasses. // need to come before subclasses.
include "Error.as" include "Error.as"
include "Math.as"
include "flash/accessibility/AccessibilityProperties.as" include "flash/accessibility/AccessibilityProperties.as"
include "flash/crypto.as" include "flash/crypto.as"

View File

@ -1,19 +1,18 @@
//! `Math` impl //! `Math` impl
use crate::avm2::activation::Activation; use crate::avm2::activation::Activation;
use crate::avm2::class::{Class, ClassAttributes};
use crate::avm2::method::{Method, NativeMethodImpl};
use crate::avm2::object::Object; use crate::avm2::object::Object;
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
use crate::avm2::Namespace;
use crate::avm2::QName;
use gc_arena::{GcCell, MutationContext};
use rand::Rng; use rand::Rng;
macro_rules! math_wrap_std { macro_rules! wrap_std {
($std:expr) => { ($name:ident, $std:expr) => {
|activation, _this, args| -> Result<Value<'_>, Error> { pub fn $name<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
if let Some(input) = args.get(0) { if let Some(input) = args.get(0) {
Ok($std(input.coerce_to_number(activation)?).into()) Ok($std(input.coerce_to_number(activation)?).into())
} else { } else {
@ -23,77 +22,20 @@ macro_rules! math_wrap_std {
}; };
} }
/// Implements `Math`'s instance initializer. wrap_std!(abs, f64::abs);
pub fn instance_init<'gc>( wrap_std!(acos, f64::acos);
_activation: &mut Activation<'_, 'gc, '_>, wrap_std!(asin, f64::asin);
_this: Option<Object<'gc>>, wrap_std!(atan, f64::atan);
_args: &[Value<'gc>], wrap_std!(ceil, f64::ceil);
) -> Result<Value<'gc>, Error> { wrap_std!(cos, f64::cos);
// TODO: Replace with actual error type. wrap_std!(exp, f64::exp);
Err("TypeError: Error #1076: Math is not a constructor.".into()) wrap_std!(floor, f64::floor);
} wrap_std!(log, f64::ln);
wrap_std!(sin, f64::sin);
wrap_std!(sqrt, f64::sqrt);
wrap_std!(tan, f64::tan);
/// Implements `Math`'s class initializer. pub fn round<'gc>(
pub fn class_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
Ok(Value::Undefined)
}
/// Construct `Math`'s class.
pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> {
let class = Class::new(
QName::new(Namespace::public(), "Math"),
Some(QName::new(Namespace::public(), "Object").into()),
Method::from_builtin(instance_init, "<Math instance initializer>", mc),
Method::from_builtin(class_init, "<Math class initializer>", mc),
mc,
);
let mut write = class.write(mc);
write.set_attributes(ClassAttributes::FINAL | ClassAttributes::SEALED);
use std::f64::consts::*;
const CONSTANTS: &[(&str, f64)] = &[
("E", E),
("LN10", LN_10),
("LN2", LN_2),
("LOG10E", LOG10_E),
("LOG2E", LOG2_E),
("PI", PI),
("SQRT1_2", FRAC_1_SQRT_2),
("SQRT2", SQRT_2),
];
write.define_public_constant_number_class_traits(CONSTANTS);
const PUBLIC_CLASS_METHODS: &[(&str, NativeMethodImpl)] = &[
("atan2", atan2),
("max", max),
("min", min),
("pow", pow),
("random", random),
("round", round),
("abs", math_wrap_std!(f64::abs)),
("acos", math_wrap_std!(f64::acos)),
("asin", math_wrap_std!(f64::asin)),
("atan", math_wrap_std!(f64::atan)),
("ceil", math_wrap_std!(f64::ceil)),
("cos", math_wrap_std!(f64::cos)),
("exp", math_wrap_std!(f64::exp)),
("floor", math_wrap_std!(f64::floor)),
("log", math_wrap_std!(f64::ln)),
("sin", math_wrap_std!(f64::sin)),
("sqrt", math_wrap_std!(f64::sqrt)),
("tan", math_wrap_std!(f64::tan)),
];
write.define_public_builtin_class_methods(mc, PUBLIC_CLASS_METHODS);
class
}
fn round<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>, _this: Option<Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
@ -108,7 +50,7 @@ fn round<'gc>(
Ok(f64::NAN.into()) Ok(f64::NAN.into())
} }
fn atan2<'gc>( pub fn atan2<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>, _this: Option<Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
@ -124,7 +66,7 @@ fn atan2<'gc>(
Ok(f64::atan2(y, x).into()) Ok(f64::atan2(y, x).into())
} }
fn max<'gc>( pub fn max<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>, _this: Option<Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
@ -141,7 +83,7 @@ fn max<'gc>(
Ok(cur_max.into()) Ok(cur_max.into())
} }
fn min<'gc>( pub fn min<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>, _this: Option<Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],
@ -158,7 +100,7 @@ fn min<'gc>(
Ok(cur_min.into()) Ok(cur_min.into())
} }
fn pow<'gc>( pub fn pow<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
_this: Option<Object<'gc>>, _this: Option<Object<'gc>>,
args: &[Value<'gc>], args: &[Value<'gc>],