Skip to content

Commit

Permalink
core: Handle the case where frame rate is set to 0 or NaN
Browse files Browse the repository at this point in the history
  • Loading branch information
Toad06 authored and kjarosh committed Nov 6, 2024
1 parent e795e68 commit 9eca71f
Show file tree
Hide file tree
Showing 14 changed files with 130 additions and 7 deletions.
9 changes: 7 additions & 2 deletions core/src/avm2/globals/flash/display/stage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,12 @@ pub fn get_frame_rate<'gc>(
_this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
Ok((*activation.context.frame_rate).into())
let mut frame_rate = *activation.context.frame_rate;
if frame_rate < 0.0 {
// Uncommon frame rate from the SWF header.
frame_rate = frame_rate.rem_euclid(256.0);
}
Ok(frame_rate.into())
}

/// Implement `frameRate`'s setter
Expand All @@ -228,7 +233,7 @@ pub fn set_frame_rate<'gc>(
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if !activation.context.forced_frame_rate {
let new_frame_rate = args.get_f64(activation, 0)?;
let new_frame_rate = args.get_f64(activation, 0)?.clamp(0.01, 1000.0);
*activation.context.frame_rate = new_frame_rate;
}

Expand Down
19 changes: 14 additions & 5 deletions core/src/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ impl Player {
if self.recent_run_frame_timings.is_empty() {
5
} else {
let frame_time = 1000.0 / self.frame_rate;
let frame_time = self.frame_time(1000.0);
let average_run_frame_time = self.recent_run_frame_timings.iter().sum::<f64>()
/ self.recent_run_frame_timings.len() as f64;
((frame_time / average_run_frame_time) as u32).clamp(1, MAX_FRAMES_PER_TICK)
Expand All @@ -489,11 +489,19 @@ impl Player {
}
}

fn frame_time(&self, time_unit: f64) -> f64 {
let frame_rate = self.frame_rate;
if frame_rate == 0.0 || frame_rate.is_nan() {
0.0
} else {
time_unit / frame_rate
}
}

pub fn tick(&mut self, dt: f64) {
if self.is_playing() {
self.frame_accumulator += dt;
let frame_rate = self.frame_rate;
let frame_time = 1000.0 / frame_rate;
let frame_time = self.frame_time(1000.0);

let max_frames_per_tick = self.max_frames_per_tick();
let mut frame = 0;
Expand Down Expand Up @@ -556,7 +564,7 @@ impl Player {
/// Returns the approximate duration of time until the next frame is due to run.
/// This is only an approximation to be used for sleep durations.
pub fn time_til_next_frame(&self) -> std::time::Duration {
let frame_time = 1000.0 / self.frame_rate;
let frame_time = self.frame_time(1000.0);
let mut dt = if self.frame_accumulator <= 0.0 {
frame_time
} else if self.frame_accumulator >= frame_time {
Expand Down Expand Up @@ -1874,7 +1882,8 @@ impl Player {

#[instrument(level = "debug", skip_all)]
pub fn run_frame(&mut self) {
let frame_time = Duration::from_nanos((750_000_000.0 / self.frame_rate) as u64);
let frame_time = self.frame_time(750_000_000.0);
let frame_time = Duration::from_nanos(frame_time as u64);
let (mut execution_limit, may_execute_while_streaming) = match self.load_behavior {
LoadBehavior::Streaming => (
ExecutionLimit::with_max_ops_and_time(10000, frame_time),
Expand Down
7 changes: 7 additions & 0 deletions tests/tests/swfs/avm2/stage_framerate_nan/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// stage.frameRate (SWF header = 30)
30
// stage.frameRate = NaN;
NaN
// Event.ENTER_FRAME
// Event.ENTER_FRAME
// Event.ENTER_FRAME
31 changes: 31 additions & 0 deletions tests/tests/swfs/avm2/stage_framerate_nan/source.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Test made using JPEXS Free Flash Decompiler.
// The frame rate in the SWF header is set to 30.

package
{
import flash.display.Sprite;
import flash.events.Event;

public class Test extends Sprite
{
public function Test()
{
super();
this.init();
}

private function init() : void
{
var enterFrame = function()
{
trace("// Event.ENTER_FRAME");
};
addEventListener(Event.ENTER_FRAME, enterFrame);
trace("// stage.frameRate (SWF header = 30)");
trace(stage.frameRate);
trace("// stage.frameRate = NaN;");
stage.frameRate = NaN;
trace(stage.frameRate);
}
}
}
Binary file not shown.
1 change: 1 addition & 0 deletions tests/tests/swfs/avm2/stage_framerate_nan/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
num_frames = 4
6 changes: 6 additions & 0 deletions tests/tests/swfs/avm2/stage_framerate_negative/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// stage.frameRate (SWF header = -43.75)
212.25
// stage.frameRate = -43.75;
0.01
// stage.frameRate = 212.25;
212.25
28 changes: 28 additions & 0 deletions tests/tests/swfs/avm2/stage_framerate_negative/source.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Test made using JPEXS Free Flash Decompiler.
// The frame rate in the SWF header is set to -43.75.

package
{
import flash.display.Sprite;

public class Test extends Sprite
{
public function Test()
{
super();
this.init();
}

private function init() : void
{
trace("// stage.frameRate (SWF header = -43.75)");
trace(stage.frameRate);
trace("// stage.frameRate = -43.75;");
stage.frameRate = -43.75;
trace(stage.frameRate);
trace("// stage.frameRate = 212.25;");
stage.frameRate = 212.25;
trace(stage.frameRate);
}
}
}
Binary file not shown.
1 change: 1 addition & 0 deletions tests/tests/swfs/avm2/stage_framerate_negative/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
num_frames = 1
6 changes: 6 additions & 0 deletions tests/tests/swfs/avm2/stage_framerate_zero/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// stage.frameRate (SWF header = 0.0)
0
// stage.frameRate = 0;
0.01
// stage.frameRate = 2000;
1000
28 changes: 28 additions & 0 deletions tests/tests/swfs/avm2/stage_framerate_zero/source.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Test made using JPEXS Free Flash Decompiler.
// The frame rate in the SWF header is set to 0.0.

package
{
import flash.display.Sprite;

public class Test extends Sprite
{
public function Test()
{
super();
this.init();
}

private function init() : void
{
trace("// stage.frameRate (SWF header = 0.0)");
trace(stage.frameRate);
trace("// stage.frameRate = 0;");
stage.frameRate = 0;
trace(stage.frameRate);
trace("// stage.frameRate = 2000;");
stage.frameRate = 2000;
trace(stage.frameRate);
}
}
}
Binary file added tests/tests/swfs/avm2/stage_framerate_zero/test.swf
Binary file not shown.
1 change: 1 addition & 0 deletions tests/tests/swfs/avm2/stage_framerate_zero/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
num_frames = 1

0 comments on commit 9eca71f

Please sign in to comment.