Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/core/platforms/Platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ export interface PlatformSettings {
* Optional provided canvas element to use for rendering. If not provided, the platform will create its own canvas element.
*/
canvas?: HTMLCanvasElement | null;

/**
* Request Animation Frame Error Callback
*
* @remarks
* If the render loop throws an error, this callback will be called.
*
* @param error The error that was thrown
*/
handleLoopError?: (error: unknown) => void;
}

export abstract class Platform {
Expand Down
75 changes: 43 additions & 32 deletions src/core/platforms/web/WebPlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,46 +120,57 @@ export class WebPlatform extends Platform {
return;
}

stage.updateFrameTime();
stage.updateAnimations();

if (!stage.hasSceneUpdates()) {
// We still need to calculate the fps else it looks like the app is frozen
stage.calculateFps();

if (targetFrameTime > 0) {
// Use setTimeout for throttled idle frames
setTimeout(
() => requestAnimationFrame(runLoop),
Math.max(targetFrameTime, 16.666666666666668),
);
} else {
// Use standard idle timeout when not throttling
setTimeout(() => requestAnimationFrame(runLoop), 16.666666666666668);
}
try {
stage.updateFrameTime();
stage.updateAnimations();

if (!stage.hasSceneUpdates()) {
// We still need to calculate the fps else it looks like the app is frozen
stage.calculateFps();

if (targetFrameTime > 0) {
// Use setTimeout for throttled idle frames
setTimeout(
() => requestAnimationFrame(runLoop),
Math.max(targetFrameTime, 16.666666666666668),
);
} else {
// Use standard idle timeout when not throttling
setTimeout(
() => requestAnimationFrame(runLoop),
16.666666666666668,
);
}

if (isIdle === false) {
stage.shManager.cleanup();
stage.eventBus.emit('idle');
isIdle = true;
}

if (stage.txMemManager.checkCleanup() === true) {
stage.txMemManager.cleanup();
}

if (isIdle === false) {
stage.shManager.cleanup();
stage.eventBus.emit('idle');
isIdle = true;
stage.flushFrameEvents();
return;
}

if (stage.txMemManager.checkCleanup() === true) {
stage.txMemManager.cleanup();
if (isIdle === true) {
stage.eventBus.emit('active');
isIdle = false;
}

stage.drawFrame();
stage.flushFrameEvents();
return;
}

if (isIdle === true) {
stage.eventBus.emit('active');
isIdle = false;
} catch (error: unknown) {
if (this.settings.handleLoopError) {
this.settings.handleLoopError(error);
} else {
throw error;
}
}

stage.drawFrame();
stage.flushFrameEvents();

// Schedule next frame
if (targetFrameTime > 0) {
// Use setTimeout + rAF combination for precise FPS control
Expand Down
1 change: 1 addition & 0 deletions src/main-api/Renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ export class RendererMain extends EventEmitter {
createImageBitmapSupport: settings.createImageBitmapSupport || 'full',
platform: settings.platform || WebPlatform,
maxRetryCount: settings.maxRetryCount ?? 5,
handleLoopError: settings.handleLoopError,
};

const {
Expand Down
Loading