Files
RetroArch/os/emscripten/library_platform_emscripten.js
T
LibretroAdmin 5b3ec8d48a Emscripten: its JavaScript runtime moves to os/emscripten
The platform, audio and camera libraries, the pre-js and the closure
externs the web build links in live under os/emscripten.
2026-10-29 20:33:27 +01:00

264 lines
12 KiB
JavaScript

//"use strict";
var LibraryPlatformEmscripten = {
$RPE: {
canvasWidth: 0,
canvasHeight: 0,
battery: null,
observer: null,
memoryUsageTimeout: null,
sentinelPromise: null,
wakeLockRequested: 0,
command_queue: [],
command_reply_queue: []
},
PlatformEmscriptenKeepThreadAlive: function() {
// keeps the thread alive until exited explicitly
{{{ runtimeKeepalivePush() }}}
},
$PlatformEmscriptenOnWindowResize: function() {
RPE.observer.unobserve(Module.canvas);
RPE.observer.observe(Module.canvas);
},
PlatformEmscriptenWatchCanvasSizeAndDpr__deps: ["platform_emscripten_update_canvas_dimensions_cb", "$PlatformEmscriptenOnWindowResize"],
PlatformEmscriptenWatchCanvasSizeAndDpr__proxy: "sync",
PlatformEmscriptenWatchCanvasSizeAndDpr: function(dpr) {
if (RPE.observer) {
RPE.observer.unobserve(Module.canvas);
RPE.observer.observe(Module.canvas);
return;
}
RPE.observer = new ResizeObserver(function(e) {
var entry = e.find(i => i.target == Module.canvas);
if (!entry) return;
if (entry.devicePixelContentBoxSize) {
RPE.canvasWidth = entry.devicePixelContentBoxSize[0].inlineSize;
RPE.canvasHeight = entry.devicePixelContentBoxSize[0].blockSize;
} else {
RPE.canvasWidth = Math.round(entry.contentRect.width * window.devicePixelRatio);
RPE.canvasHeight = Math.round(entry.contentRect.height * window.devicePixelRatio);
}
// doubles are too big to pass as an argument to exported functions
{{{ makeSetValue("dpr", "0", "window.devicePixelRatio", "double") }}};
_platform_emscripten_update_canvas_dimensions_cb(RPE.canvasWidth, RPE.canvasHeight, dpr);
});
RPE.observer.observe(Module.canvas);
window.addEventListener("resize", PlatformEmscriptenOnWindowResize, false);
// first frame: this might be off by 1, but it will be corrected afterward
RPE.canvasWidth = Math.round(Module.canvas.clientWidth * window.devicePixelRatio);
RPE.canvasHeight = Math.round(Module.canvas.clientHeight * window.devicePixelRatio);
{{{ makeSetValue("dpr", "0", "window.devicePixelRatio", "double") }}};
_platform_emscripten_update_canvas_dimensions_cb(RPE.canvasWidth, RPE.canvasHeight, dpr);
},
$PlatformEmscriptenOnCanvasPointerDown: function() {
Module.canvas.focus();
},
$PlatformEmscriptenOnCanvasContextMenu: function(e) {
e.preventDefault();
},
PlatformEmscriptenCanvasListenersInit__deps: ["$PlatformEmscriptenOnCanvasPointerDown", "$PlatformEmscriptenOnCanvasContextMenu"],
PlatformEmscriptenCanvasListenersInit__proxy: "sync",
PlatformEmscriptenCanvasListenersInit: function() {
Module.canvas.addEventListener("pointerdown", PlatformEmscriptenOnCanvasPointerDown, false);
Module.canvas.addEventListener("contextmenu", PlatformEmscriptenOnCanvasContextMenu, false);
},
$PlatformEmscriptenOnVisibilityChange__deps: ["platform_emscripten_update_window_hidden_cb", "$PlatformEmscriptenRefreshWakeLock"],
$PlatformEmscriptenOnVisibilityChange: function() {
_platform_emscripten_update_window_hidden_cb(document.visibilityState == "hidden");
if (document.visibilityState != "hidden") PlatformEmscriptenRefreshWakeLock();
},
PlatformEmscriptenWatchWindowVisibility__deps: ["$PlatformEmscriptenOnVisibilityChange"],
PlatformEmscriptenWatchWindowVisibility__proxy: "sync",
PlatformEmscriptenWatchWindowVisibility: function() {
document.addEventListener("visibilitychange", PlatformEmscriptenOnVisibilityChange, false);
},
$PlatformEmscriptenOnPowerStateChange__deps: ["platform_emscripten_update_power_state_cb"],
$PlatformEmscriptenOnPowerStateChange: function(e) {
_platform_emscripten_update_power_state_cb(true, Number.isFinite(e.target.dischargingTime) ? e.target.dischargingTime : 0x7FFFFFFF, e.target.level, e.target.charging);
},
PlatformEmscriptenPowerStateInit__deps: ["$PlatformEmscriptenOnPowerStateChange"],
PlatformEmscriptenPowerStateInit__proxy: "sync",
PlatformEmscriptenPowerStateInit: function() {
if (!navigator.getBattery) return;
navigator.getBattery().then(function(battery) {
RPE.battery = battery;
battery.addEventListener("chargingchange", PlatformEmscriptenOnPowerStateChange);
battery.addEventListener("levelchange", PlatformEmscriptenOnPowerStateChange);
PlatformEmscriptenOnPowerStateChange({target: battery});
});
},
$PlatformEmscriptenUpdateMemoryUsage__deps: ["platform_emscripten_update_memory_usage_cb"],
$PlatformEmscriptenUpdateMemoryUsage: function() {
// unfortunately this will be inaccurate in threaded (worker) builds
_platform_emscripten_update_memory_usage_cb(BigInt(performance.memory.usedJSHeapSize || 0), BigInt(performance.memory.jsHeapSizeLimit || 0));
RPE.memoryUsageTimeout = setTimeout(PlatformEmscriptenUpdateMemoryUsage, 5000);
},
PlatformEmscriptenMemoryUsageInit__deps: ["$PlatformEmscriptenUpdateMemoryUsage"],
PlatformEmscriptenMemoryUsageInit: function() {
if (!performance.memory) return;
PlatformEmscriptenUpdateMemoryUsage();
},
$PlatformEmscriptenOnFullscreenChange__deps: ["platform_emscripten_update_fullscreen_state_cb"],
$PlatformEmscriptenOnFullscreenChange: function() {
_platform_emscripten_update_fullscreen_state_cb(!!document.fullscreenElement);
},
PlatformEmscriptenWatchFullscreen__deps: ["$PlatformEmscriptenOnFullscreenChange"],
PlatformEmscriptenWatchFullscreen__proxy: "sync",
PlatformEmscriptenWatchFullscreen: function() {
document.addEventListener("fullscreenchange", PlatformEmscriptenOnFullscreenChange, false);
},
$PlatformEmscriptenOnGLContextLost__deps: ["platform_emscripten_gl_context_lost_cb"],
$PlatformEmscriptenOnGLContextLost: function(e) {
e.preventDefault();
_platform_emscripten_gl_context_lost_cb();
},
$PlatformEmscriptenOnGLContextRestored__deps: ["platform_emscripten_gl_context_restored_cb"],
$PlatformEmscriptenOnGLContextRestored: function() {
_platform_emscripten_gl_context_restored_cb();
},
PlatformEmscriptenGLContextEventInit__deps: ["$PlatformEmscriptenOnGLContextLost", "$PlatformEmscriptenOnGLContextRestored"],
PlatformEmscriptenGLContextEventInit: function() {
Module.canvas.addEventListener("webglcontextlost", PlatformEmscriptenOnGLContextLost);
Module.canvas.addEventListener("webglcontextrestored", PlatformEmscriptenOnGLContextRestored);
},
$PlatformEmscriptenDoSetCanvasSize: async function(width, height) {
// window.resizeTo is terrible; it uses window.outerWidth/outerHeight dimensions,
// which are on their own scale entirely, which also changes with the zoom level independently from the DPR.
// instead try 2 hardcoded sizes first and interpolate to find the correct size.
var expAX = 600;
var expAY = 450;
var expBX = expAX + 100;
var expBY = expAY + 100;
await new Promise(r => setTimeout(r, 0));
window.resizeTo(expAX, expAY);
await new Promise(r => setTimeout(r, 50));
var oldWidth = RPE.canvasWidth;
var oldHeight = RPE.canvasHeight;
window.resizeTo(expBX, expBY);
await new Promise(r => setTimeout(r, 50));
var projX = (expBX - expAX) * (width - oldWidth) / (RPE.canvasWidth - oldWidth) + expAX;
var projY = (expBY - expAY) * (height - oldHeight) / (RPE.canvasHeight - oldHeight) + expAY;
window.resizeTo(Math.round(projX), Math.round(projY));
},
PlatformEmscriptenSetCanvasSize__proxy: "sync",
PlatformEmscriptenSetCanvasSize__deps: ["$PlatformEmscriptenDoSetCanvasSize"],
PlatformEmscriptenSetCanvasSize: function(width, height) {
// c-accessible library functions should not be async
PlatformEmscriptenDoSetCanvasSize(width, height);
},
$PlatformEmscriptenRefreshWakeLock: async function() {
// wake lock is lost when the window is hidden, and must be requested again
if (RPE.wakeLockRequested && navigator?.wakeLock?.request) {
if (RPE.sentinelPromise) {
try {
var sentinel = await RPE.sentinelPromise;
if (!sentinel.released) return;
} catch (e) {}
}
try {
RPE.sentinelPromise = navigator.wakeLock.request("screen");
RPE.sentinelPromise.catch(function() {
RPE.sentinelPromise = null;
});
} catch (e) {}
}
},
$PlatformEmscriptenDoSetWakeLock: async function(state) {
RPE.wakeLockRequested = state;
if (state && !RPE.sentinelPromise && document.visibilityState != "hidden" && navigator?.wakeLock?.request) {
try {
RPE.sentinelPromise = navigator.wakeLock.request("screen");
RPE.sentinelPromise.catch(function() {
RPE.sentinelPromise = null;
});
} catch (e) {}
} else if (!state && RPE.sentinelPromise) {
try {
var sentinel = await RPE.sentinelPromise;
sentinel.release();
} catch (e) {}
RPE.sentinelPromise = null;
}
},
PlatformEmscriptenSetWakeLock__proxy: "sync",
PlatformEmscriptenSetWakeLock__deps: ["$PlatformEmscriptenDoSetWakeLock"],
PlatformEmscriptenSetWakeLock: function(state) {
PlatformEmscriptenDoSetWakeLock(state);
},
PlatformEmscriptenGetSystemInfo: function(browserPtr, osPtr) {
var browser = 0;
var os = 0;
var userAgent = navigator?.userAgent?.toLowerCase?.();
if (userAgent) {
browser = 1 + ["chrom", "firefox", "safari"].findIndex(i => userAgent.includes(i));
os = 1 + [/windows/, /linux|cros|android/, /iphone|ipad/, /mac os/].findIndex(i => i.test(userAgent));
}
{{{ makeSetValue("browserPtr", "0", "browser", "u32") }}};
{{{ makeSetValue("osPtr", "0", "os", "u32") }}};
},
$EmscriptenSendCommand__deps: ["platform_emscripten_command_raise_flag"],
$EmscriptenSendCommand: function(str) {
RPE.command_queue.push(str);
_platform_emscripten_command_raise_flag();
},
$EmscriptenReceiveCommandReply: function() {
return RPE.command_reply_queue.shift();
},
$PlatformEmscriptenFreeBrowser__proxy: "sync",
$PlatformEmscriptenFreeBrowser__deps: ["$PlatformEmscriptenDoSetWakeLock", "$PlatformEmscriptenOnCanvasPointerDown", "$PlatformEmscriptenOnCanvasContextMenu", "$PlatformEmscriptenOnWindowResize", "$PlatformEmscriptenOnVisibilityChange", "$PlatformEmscriptenOnFullscreenChange", "$PlatformEmscriptenOnPowerStateChange"],
$PlatformEmscriptenFreeBrowser: function() {
if (RPE.memoryUsageTimeout) clearTimeout(RPE.memoryUsageTimeout);
PlatformEmscriptenDoSetWakeLock(false);
if (RPE.observer) {
RPE.observer.unobserve(Module.canvas);
RPE.observer = null;
}
Module.canvas.removeEventListener("pointerdown", PlatformEmscriptenOnCanvasPointerDown);
Module.canvas.removeEventListener("contextmenu", PlatformEmscriptenOnCanvasContextMenu);
window.removeEventListener("resize", PlatformEmscriptenOnWindowResize);
document.removeEventListener("visibilitychange", PlatformEmscriptenOnVisibilityChange);
document.removeEventListener("fullscreenchange", PlatformEmscriptenOnFullscreenChange);
if (RPE.battery) {
RPE.battery.removeEventListener("chargingchange", PlatformEmscriptenOnPowerStateChange);
RPE.battery.removeEventListener("levelchange", PlatformEmscriptenOnPowerStateChange);
RPE.battery = null;
}
},
PlatformEmscriptenFree__deps: ["$PlatformEmscriptenFreeBrowser"],
PlatformEmscriptenFree: function() {
Module.canvas.removeEventListener("webglcontextlost", PlatformEmscriptenOnGLContextLost);
Module.canvas.removeEventListener("webglcontextrestored", PlatformEmscriptenOnGLContextRestored);
PlatformEmscriptenFreeBrowser();
}
};
autoAddDeps(LibraryPlatformEmscripten, '$RPE');
addToLibrary(LibraryPlatformEmscripten);