@@ -29,6 +29,7 @@ export class WebPlatform extends Platform {
2929 override startLoop ( stage : Stage ) : void {
3030 let isIdle = false ;
3131 let lastFrameTime = 0 ;
32+ const buffer = 4 ;
3233
3334 const runLoop = ( currentTime : number = 0 ) => {
3435 const targetFrameTime = stage . targetFrameTime ;
@@ -39,7 +40,13 @@ export class WebPlatform extends Platform {
3940
4041 // If not enough time has passed, skip this frame
4142 if ( elapsed < targetFrameTime ) {
42- requestAnimationFrame ( runLoop ) ;
43+ const wait = targetFrameTime - elapsed ;
44+
45+ if ( wait > buffer ) {
46+ setTimeout ( ( ) => requestAnimationFrame ( runLoop ) , wait - buffer ) ;
47+ } else {
48+ requestAnimationFrame ( runLoop ) ;
49+ }
4350 return ;
4451 }
4552
@@ -56,16 +63,13 @@ export class WebPlatform extends Platform {
5663 // We still need to calculate the fps else it looks like the app is frozen
5764 stage . calculateFps ( ) ;
5865
59- if ( targetFrameTime > 0 ) {
60- // Use setTimeout for throttled idle frames
61- setTimeout (
62- ( ) => requestAnimationFrame ( runLoop ) ,
63- Math . max ( targetFrameTime , 16.666666666666668 ) ,
64- ) ;
65- } else {
66- // Use standard idle timeout when not throttling
67- setTimeout ( ( ) => requestAnimationFrame ( runLoop ) , 16.666666666666668 ) ;
68- }
66+ // We use 15ms instead of 16.6ms to provide a safety buffer.
67+ // This ensures we wake up slightly before the next frame to check for updates,
68+ // preventing us from missing a frame due to timer variances.
69+ setTimeout (
70+ ( ) => requestAnimationFrame ( runLoop ) ,
71+ Math . max ( targetFrameTime , 15 ) ,
72+ ) ;
6973
7074 if ( isIdle === false ) {
7175 stage . shManager . cleanup ( ) ;
@@ -86,7 +90,22 @@ export class WebPlatform extends Platform {
8690 stage . flushFrameEvents ( ) ;
8791
8892 // Schedule next frame
89- requestAnimationFrame ( runLoop ) ;
93+ if ( targetFrameTime > 0 ) {
94+ const nextTarget = lastFrameTime + targetFrameTime ;
95+ const now = performance . now ( ) ;
96+ const wait = nextTarget - now ;
97+
98+ // If we have a significant wait time, use setTimeout to yield to the browser.
99+ // We subtract a small buffer (4ms) to ensure we wake up BEFORE the next frame.
100+ if ( wait > buffer ) {
101+ setTimeout ( ( ) => requestAnimationFrame ( runLoop ) , wait - buffer ) ;
102+ } else {
103+ requestAnimationFrame ( runLoop ) ;
104+ }
105+ } else {
106+ // Use standard rAF when not throttling
107+ requestAnimationFrame ( runLoop ) ;
108+ }
90109 } ;
91110 requestAnimationFrame ( runLoop ) ;
92111 }
0 commit comments