v0.7.0: SDL-free wasm worker backend (WebGPU + WebGL2) with on-demand assets
The engine now runs on a plain Web Worker (no SDL, no PROXY_TO_PTHREAD: in a pthread Emscripten proxies every filesystem syscall to the main browser thread, where synchronous XHR - and thus FS.createLazyFile - is forbidden). A page/worker pair transfers an OffscreenCanvas and forwards DOM input; assets load lazily from an embedded manifest, so entering a map fetches only that map and its tiles. Presentation: ra3.webgpu (WebGPU via Emscripten's emdawnwebgpu port, WGSL shaders, the default on wasm) and ra3.wasmgl (WebGL2, GLSL ES). The legacy SDL ra3.webgl backend is removed.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
<!doctype html>
|
||||
<!-- OpenRA3 web page. The engine runs inside openra3.worker.js: the page owns the
|
||||
DOM (input, resize) and transfers #canvas to the worker as an OffscreenCanvas,
|
||||
so the engine never blocks the main thread. -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
<title>OpenRA3</title>
|
||||
<style>
|
||||
html, body { margin: 0; height: 100%; background: #1a0a08; overflow: hidden; }
|
||||
#canvas { display: block; width: 100vw; height: 100vh; outline: none; cursor: default; }
|
||||
#loading { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
|
||||
color: #ecbe50; font: 16px/1.4 monospace; pointer-events: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas" tabindex="-1" oncontextmenu="event.preventDefault()"></canvas>
|
||||
<div id="loading">Loading OpenRA3…</div>
|
||||
<script>
|
||||
(function () {
|
||||
var canvas = document.getElementById('canvas');
|
||||
var loading = document.getElementById('loading');
|
||||
// `?nocache` busts the worker, module and wasm fetches (development),
|
||||
// so a rebuild is picked up without clearing the browser cache.
|
||||
var bust = location.search.indexOf('nocache') >= 0 ? Date.now() : 0;
|
||||
var worker = new Worker('openra3.worker.js' + (bust ? '?v=' + bust : ''));
|
||||
|
||||
var ready = false;
|
||||
var pending = []; // input queued until the runtime is up
|
||||
|
||||
function post(message) {
|
||||
if (ready) {
|
||||
worker.postMessage(message);
|
||||
} else {
|
||||
pending.push(message);
|
||||
}
|
||||
}
|
||||
|
||||
function backingSize() {
|
||||
var dpr = window.devicePixelRatio || 1;
|
||||
return {
|
||||
width: Math.max(1, Math.round(window.innerWidth * dpr)),
|
||||
height: Math.max(1, Math.round(window.innerHeight * dpr)),
|
||||
};
|
||||
}
|
||||
|
||||
worker.onmessage = function (event) {
|
||||
var data = event.data || {};
|
||||
if (data.log) { console.log(data.log); return; }
|
||||
if (data.ready) {
|
||||
ready = true;
|
||||
if (loading) loading.remove();
|
||||
for (var i = 0; i < pending.length; i++) worker.postMessage(pending[i]);
|
||||
pending = [];
|
||||
return;
|
||||
}
|
||||
if (typeof data.openra3Frames === 'number') { window.openra3Frames = data.openra3Frames; }
|
||||
};
|
||||
|
||||
// Hand the canvas to the worker as an OffscreenCanvas. After this the
|
||||
// page can no longer size or draw it; the worker owns it.
|
||||
var size = backingSize();
|
||||
var offscreen = canvas.transferControlToOffscreen();
|
||||
worker.postMessage({ canvas: offscreen, width: size.width, height: size.height, bust: bust }, [offscreen]);
|
||||
|
||||
// ---- input: the page owns the DOM, the worker owns the game --------
|
||||
window.addEventListener('keydown', function (event) {
|
||||
post({ type: 'key', code: event.code, down: true });
|
||||
if (event.code === 'Tab' || event.code === 'Space' || event.code.indexOf('Arrow') === 0) event.preventDefault();
|
||||
}, false);
|
||||
window.addEventListener('keyup', function (event) {
|
||||
post({ type: 'key', code: event.code, down: false });
|
||||
}, false);
|
||||
window.addEventListener('keypress', function (event) {
|
||||
if (event.charCode) post({ type: 'text', char: event.charCode });
|
||||
}, false);
|
||||
|
||||
canvas.addEventListener('mousedown', function (event) {
|
||||
post({ type: 'mousebutton', x: event.clientX, y: event.clientY, left: event.button === 0 });
|
||||
}, false);
|
||||
window.addEventListener('mousemove', function (event) {
|
||||
post({ type: 'mousemove', x: event.clientX, y: event.clientY, dx: event.movementX, dy: event.movementY,
|
||||
left: (event.buttons & 1) !== 0 });
|
||||
}, false);
|
||||
window.addEventListener('wheel', function (event) {
|
||||
post({ type: 'wheel', deltaY: event.deltaY });
|
||||
}, { passive: true });
|
||||
window.addEventListener('contextmenu', function (event) { event.preventDefault(); });
|
||||
|
||||
window.addEventListener('resize', function () {
|
||||
var next = backingSize();
|
||||
post({ type: 'resize', width: next.width, height: next.height });
|
||||
});
|
||||
window.addEventListener('beforeunload', function () {
|
||||
if (ready) worker.postMessage({ type: 'quit' });
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user