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.
115 lines
4.3 KiB
JavaScript
115 lines
4.3 KiB
JavaScript
'use strict';
|
|
// OpenRA3 WebAssembly worker bootstrap.
|
|
//
|
|
// The whole Emscripten runtime runs here (not on the page main thread), so the
|
|
// runtime's filesystem is local to this worker: `FS.createLazyFile` can use
|
|
// synchronous XHR to fetch assets on demand without blocking the page.
|
|
//
|
|
// Protocol with index.html:
|
|
// page -> worker { canvas, width, height } (once, transfers the OffscreenCanvas)
|
|
// page -> worker { type: 'key' | 'text' | 'mousebutton' | 'mousemove' | 'wheel' | 'resize' | 'quit', ... }
|
|
// worker -> page { log } | { ready } | { openra3Frames }
|
|
//
|
|
// The Emscripten runtime is started by importScripts('openra3.js') once the
|
|
// canvas has arrived; main() then runs here and yields via Asyncify.
|
|
|
|
// Forward worker warnings/errors (including WebGPU validation messages from the
|
|
// runtime) to the page, which logs them to the main console.
|
|
(function () {
|
|
var origError = console.error.bind(console);
|
|
var origWarn = console.warn.bind(console);
|
|
console.error = function () {
|
|
origError.apply(null, arguments);
|
|
try { self.postMessage({ log: '[worker error] ' + Array.prototype.join.call(arguments, ' ') }); } catch (e) {}
|
|
};
|
|
console.warn = function () {
|
|
origWarn.apply(null, arguments);
|
|
try { self.postMessage({ log: '[worker warn] ' + Array.prototype.join.call(arguments, ' ') }); } catch (e) {}
|
|
};
|
|
})();
|
|
|
|
self.Module = {
|
|
print: function (text) {
|
|
self.postMessage({ log: String(text) });
|
|
},
|
|
printErr: function (text) {
|
|
self.postMessage({ log: String(text) });
|
|
},
|
|
onRuntimeInitialized: function () {
|
|
self.postMessage({ ready: true });
|
|
},
|
|
};
|
|
|
|
var openra3Started = false;
|
|
|
|
self.onmessage = function (event) {
|
|
var data = event.data;
|
|
if (!data) return;
|
|
|
|
if (!openra3Started) {
|
|
if (!data.canvas) return;
|
|
openra3Started = true;
|
|
var canvas = data.canvas;
|
|
if (data.width && data.height) {
|
|
canvas.width = data.width;
|
|
canvas.height = data.height;
|
|
}
|
|
self.Module.canvas = canvas;
|
|
|
|
var start = function () {
|
|
// `?nocache` on the page forces a fresh module + wasm fetch, so a
|
|
// rebuild is picked up without clearing the browser cache.
|
|
var bust = data.bust ? ('?v=' + data.bust) : '';
|
|
if (bust) self.Module.locateFile = function (path) { return path + bust; };
|
|
try {
|
|
importScripts('openra3.js' + bust);
|
|
} catch (error) {
|
|
self.postMessage({ log: 'OpenRA3 worker failed to start: ' + error });
|
|
}
|
|
};
|
|
|
|
// The WebGPU backend starts from a device created here (`Module.
|
|
// preinitializedWebGPUDevice`); the WebGL backend ignores it. If WebGPU
|
|
// is unavailable the runtime falls back to WebGL.
|
|
if (typeof navigator !== 'undefined' && navigator.gpu) {
|
|
navigator.gpu.requestAdapter().then(function (adapter) {
|
|
if (!adapter) { start(); return; }
|
|
adapter.requestDevice().then(function (device) {
|
|
self.Module.preinitializedWebGPUDevice = device;
|
|
start();
|
|
}).catch(start);
|
|
}).catch(start);
|
|
} else {
|
|
start();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Input is only meaningful once the runtime has initialized its exports.
|
|
switch (data.type) {
|
|
case 'key':
|
|
if (self.Module.ccall) self.Module.ccall('openra3_key', null, ['string', 'number'], [data.code, data.down ? 1 : 0]);
|
|
break;
|
|
case 'text':
|
|
if (self.Module._openra3_text) self.Module._openra3_text(data.char | 0);
|
|
break;
|
|
case 'mousebutton':
|
|
if (self.Module._openra3_mouse_button) self.Module._openra3_mouse_button(data.x, data.y, data.left ? 1 : 0);
|
|
break;
|
|
case 'mousemove':
|
|
if (self.Module._openra3_mouse_move) self.Module._openra3_mouse_move(data.x, data.y, data.dx, data.dy, data.left ? 1 : 0);
|
|
break;
|
|
case 'wheel':
|
|
if (self.Module._openra3_wheel) self.Module._openra3_wheel(data.deltaY);
|
|
break;
|
|
case 'resize':
|
|
if (self.Module._openra3_resize) self.Module._openra3_resize(data.width | 0, data.height | 0);
|
|
break;
|
|
case 'quit':
|
|
if (self.Module._openra3_quit) self.Module._openra3_quit();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
};
|