From 37ec94f95bfa5c1f83f481962600d04ab2497b74 Mon Sep 17 00:00:00 2001 From: golfinq Date: Sat, 28 Jan 2023 23:38:59 -0500 Subject: [PATCH] Avm2: Implement Stage.invalidate --- core/src/avm2.rs | 2 +- core/src/avm2/globals/flash/display/stage.rs | 18 +++++++ core/src/display_object/stage.rs | 33 ++++++++++++ core/src/player.rs | 11 ++++ .../tests/swfs/avm2/stage_invalidate/Test.as | 47 ++++++++++++++++++ .../swfs/avm2/stage_invalidate/output.txt | 38 ++++++++++++++ .../tests/swfs/avm2/stage_invalidate/test.swf | Bin 0 -> 1260 bytes .../swfs/avm2/stage_invalidate/test.toml | 1 + 8 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 tests/tests/swfs/avm2/stage_invalidate/Test.as create mode 100644 tests/tests/swfs/avm2/stage_invalidate/output.txt create mode 100644 tests/tests/swfs/avm2/stage_invalidate/test.swf create mode 100644 tests/tests/swfs/avm2/stage_invalidate/test.toml diff --git a/core/src/avm2.rs b/core/src/avm2.rs index 942992119..489c46fd3 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -66,7 +66,7 @@ pub use crate::avm2::value::Value; use self::scope::Scope; -const BROADCAST_WHITELIST: [&str; 3] = ["enterFrame", "exitFrame", "frameConstructed"]; +const BROADCAST_WHITELIST: [&str; 4] = ["enterFrame", "exitFrame", "frameConstructed", "render"]; /// The state of an AVM2 interpreter. #[derive(Collect)] diff --git a/core/src/avm2/globals/flash/display/stage.rs b/core/src/avm2/globals/flash/display/stage.rs index 10054e4b1..0cfda99a9 100644 --- a/core/src/avm2/globals/flash/display/stage.rs +++ b/core/src/avm2/globals/flash/display/stage.rs @@ -726,6 +726,21 @@ pub fn stage3ds<'gc>( Ok(Value::Undefined) } +/// Implement `invalidate` +pub fn invalidate<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Option>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + if let Some(stage) = this + .and_then(|this| this.as_display_object()) + .and_then(|this| this.as_stage()) + { + stage.set_invalidated(activation.context.gc_context, true); + } + Ok(Value::Undefined) +} + /// Stage.fullScreenSourceRect's getter pub fn full_screen_source_rect<'gc>( activation: &mut Activation<'_, 'gc>, @@ -863,5 +878,8 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc> ]; write.define_public_builtin_instance_properties(mc, PUBLIC_INSTANCE_PROPERTIES); + const PUBLIC_INSTANCE_METHODS: &[(&str, NativeMethodImpl)] = &[("invalidate", invalidate)]; + write.define_public_builtin_instance_methods(mc, PUBLIC_INSTANCE_METHODS); + class } diff --git a/core/src/display_object/stage.rs b/core/src/display_object/stage.rs index aa8fd1106..71758dc8c 100644 --- a/core/src/display_object/stage.rs +++ b/core/src/display_object/stage.rs @@ -90,6 +90,9 @@ pub struct StageData<'gc> { /// The alignment of the stage. align: StageAlign, + /// Whether or not a RENDER event should be dispatched on the next render + invalidated: bool, + /// Whether to use high quality downsampling for bitmaps. /// /// This is usally implied by `quality` being `Best` or higher, but the AVM1 @@ -149,6 +152,7 @@ impl<'gc> Stage<'gc> { } else { StageDisplayState::Normal }, + invalidated: false, align: Default::default(), use_bitmap_downsampling: false, view_bounds: Default::default(), @@ -210,6 +214,16 @@ impl<'gc> Stage<'gc> { self.0.write(gc_context).loader_info = loader_info; } + // Get the invalidation state + pub fn invalidated(self) -> bool { + self.0.read().invalidated + } + + // Set the invalidation state + pub fn set_invalidated(self, gc_context: MutationContext<'gc, '_>, value: bool) { + self.0.write(gc_context).invalidated = value; + } + /// Returns the quality setting of the stage. /// /// In the Flash Player, the quality setting affects anti-aliasing and smoothing of bitmaps. @@ -617,6 +631,25 @@ impl<'gc> Stage<'gc> { } } + /// Broadcast the 'render' event + /// + /// TODO: Need additional check as Flash Player does not + /// broadcast the 'render' event on the first render + pub fn broadcast_render(&self, context: &mut UpdateContext<'_, 'gc>) { + let render_evt = Avm2EventObject::bare_default_event(context, "render"); + + let dobject_constr = context.avm2.classes().display_object; + + if let Err(e) = Avm2::broadcast_event(context, render_evt, dobject_constr) { + tracing::error!( + "Encountered AVM2 error when broadcasting render event: {}", + e + ); + } + + self.set_invalidated(context.gc_context, false); + } + /// Fires `Stage.onFullScreen` in AVM1 or `Event.FULLSCREEN` in AVM2. pub fn fire_fullscreen_event(self, context: &mut UpdateContext<'_, 'gc>) { if !context.is_action_script_3() { diff --git a/core/src/player.rs b/core/src/player.rs index e1c8396b1..3292220c4 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -1481,6 +1481,17 @@ impl Player { #[instrument(level = "debug", skip_all)] pub fn render(&mut self) { + let invalidated = self + .gc_arena + .borrow() + .mutate(|_, gc_root| gc_root.data.read().stage.invalidated()); + if invalidated { + self.update(|context| { + let stage = context.stage; + stage.broadcast_render(context); + }); + } + let (renderer, ui, transform_stack) = (&mut self.renderer, &mut self.ui, &mut self.transform_stack); let mut background_color = Color::WHITE; diff --git a/tests/tests/swfs/avm2/stage_invalidate/Test.as b/tests/tests/swfs/avm2/stage_invalidate/Test.as new file mode 100644 index 000000000..169fd3870 --- /dev/null +++ b/tests/tests/swfs/avm2/stage_invalidate/Test.as @@ -0,0 +1,47 @@ +package { + import flash.display.Sprite; + import flash.events.Event; + + public class Test extends Sprite { + private var test_stage: int = 0; + + public function Test() { + addEventListener(Event.ENTER_FRAME, enterFrameListener); + addEventListener(Event.FRAME_CONSTRUCTED, frameConstructedListener); + addEventListener(Event.EXIT_FRAME, exitFrameListener); + addEventListener(Event.RENDER, renderListener); + test_stage = 0; + } + + public function enterFrameListener(e: Event) : void { + trace("ENTER_FRAME ( test_stage = ", test_stage, " )"); + if ((test_stage == 2) || (test_stage == 14) || (test_stage == 17)) { + trace("Invalidate called inside enterFrameListener"); + stage.invalidate(); + } + test_stage = (test_stage + 1) % 21; + } + + public function frameConstructedListener(e: Event) : void { + trace("FRAME_CONSTRUCTED ( test_stage = ", test_stage, " )"); + if ((test_stage == 6) || (test_stage == 12) || (test_stage == 15)) { + trace("Invalidate called inside frameConstructedListener"); + stage.invalidate(); + } + test_stage = (test_stage + 1) % 21; + } + + public function exitFrameListener(e: Event) : void { + trace("EXIT_FRAME ( test_stage = ", test_stage, " )"); + if ((test_stage == 10) || (test_stage == 13) || (test_stage == 19)) { + trace("Invalidate called inside exitFrameListener"); + stage.invalidate(); + } + test_stage = (test_stage + 1) % 21; + } + + public function renderListener(e: Event) : void { + trace("RENDER ( test_stage = ", test_stage, " )"); + } + } +} diff --git a/tests/tests/swfs/avm2/stage_invalidate/output.txt b/tests/tests/swfs/avm2/stage_invalidate/output.txt new file mode 100644 index 000000000..9d98d05f5 --- /dev/null +++ b/tests/tests/swfs/avm2/stage_invalidate/output.txt @@ -0,0 +1,38 @@ +FRAME_CONSTRUCTED ( test_stage = 0 ) +EXIT_FRAME ( test_stage = 1 ) +ENTER_FRAME ( test_stage = 2 ) +Invalidate called inside enterFrameListener +FRAME_CONSTRUCTED ( test_stage = 3 ) +EXIT_FRAME ( test_stage = 4 ) +RENDER ( test_stage = 5 ) +ENTER_FRAME ( test_stage = 5 ) +FRAME_CONSTRUCTED ( test_stage = 6 ) +Invalidate called inside frameConstructedListener +EXIT_FRAME ( test_stage = 7 ) +RENDER ( test_stage = 8 ) +ENTER_FRAME ( test_stage = 8 ) +FRAME_CONSTRUCTED ( test_stage = 9 ) +EXIT_FRAME ( test_stage = 10 ) +Invalidate called inside exitFrameListener +RENDER ( test_stage = 11 ) +ENTER_FRAME ( test_stage = 11 ) +FRAME_CONSTRUCTED ( test_stage = 12 ) +Invalidate called inside frameConstructedListener +EXIT_FRAME ( test_stage = 13 ) +Invalidate called inside exitFrameListener +RENDER ( test_stage = 14 ) +ENTER_FRAME ( test_stage = 14 ) +Invalidate called inside enterFrameListener +FRAME_CONSTRUCTED ( test_stage = 15 ) +Invalidate called inside frameConstructedListener +EXIT_FRAME ( test_stage = 16 ) +RENDER ( test_stage = 17 ) +ENTER_FRAME ( test_stage = 17 ) +Invalidate called inside enterFrameListener +FRAME_CONSTRUCTED ( test_stage = 18 ) +EXIT_FRAME ( test_stage = 19 ) +Invalidate called inside exitFrameListener +RENDER ( test_stage = 20 ) +ENTER_FRAME ( test_stage = 20 ) +FRAME_CONSTRUCTED ( test_stage = 0 ) +EXIT_FRAME ( test_stage = 1 ) diff --git a/tests/tests/swfs/avm2/stage_invalidate/test.swf b/tests/tests/swfs/avm2/stage_invalidate/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..36acadc65ed98600a628a6504cf45e134fb21332 GIT binary patch literal 1260 zcmVYL40Q%(kttgyeO*>+NoAjI!$5 zBx$GXSUjb!WvNBasO`DgTqXyJbZy&cJ7>fzkxs`lHNw(mFVb$St-U!XJ(@Nw;$8k*8>Atp1w?&;X z=csLX=fnYEyKC!wrCc}-WHQa6#B2YrXdK2Q7O_ve5Izt2_d8j zl_4kNTAdhOyV-Ht-&4(VPl(2jci1*f4^o=;>4|Cgh-HF`K6}OzWr!Okx?b%;n|x`y z9<{0CMnd75t^20nbWQYO1-wJ-?_5q%lO>2 z?CXlN#(~#)Z%j*f8TIH|)Rh`1hnu=w`sA?mmlG#=^z3h}prob)Fj z!9BeqV@Vc$GL~f=ka1APAsNrfI4t9cjOS%xLB??zCuO`W<5d~om9Zd8n=;;##k4GC zWGO34s*LzAGm3--yz(vv&s+R?n?LWoi!r}^PguVX;{l8f7!P5ja>9BZz6(Mu9`qr> zH}@Px1yMj!8DT;2mysyoKp9Db7%C&5Ai;g$7h)9wVV_?{f)EIbef4v&ib62-?LXom z&{tvXqd~ACG_ZjUv2RE<0jCC`k-^tD{M;}%GRS=+zzyM%A=o!U+z{!nqt@JD9spRv z!B7hT(Z2e(5JS;HtP$Qy^o_Z##adWQ4dU>+j^Fnl^M4tcAC8=$)}<=tco{|q5KGg}z(Zh`J5 z=dDe6w`X_?@T4*C?vQtPa1V5AQ{Fn`g=PqmnOS1QyAQfgIPbxP_i%=n0-lfY)}Wr7 zL*C|K3v|kqmxdre^gy84S0T(tQ6pg_)W)K*s5X|2CAE<>l4@hwSXLWxBhDYM&>H`= WVxSNqK`=4-`*;q29sdF5{)o2?OpM_G literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm2/stage_invalidate/test.toml b/tests/tests/swfs/avm2/stage_invalidate/test.toml new file mode 100644 index 000000000..fc915e37e --- /dev/null +++ b/tests/tests/swfs/avm2/stage_invalidate/test.toml @@ -0,0 +1 @@ +num_frames = 8