Customize the web client HTML file

Modify the client HTML file when copying it to the build directory to
apply current CLIENTBIN and BASEGAME. It always loads engine/data from
the current directory (no need to try to locate the build directory).
This commit is contained in:
Zack Middleton 2024-06-09 23:24:12 -05:00
parent e6c0776d98
commit 8365ea7ed2
4 changed files with 111 additions and 78 deletions

View file

@ -291,6 +291,7 @@ LIBTOMCRYPTSRCDIR=$(AUTOUPDATERSRCDIR)/rsa_tools/libtomcrypt-1.17
TOMSFASTMATHSRCDIR=$(AUTOUPDATERSRCDIR)/rsa_tools/tomsfastmath-0.13.1 TOMSFASTMATHSRCDIR=$(AUTOUPDATERSRCDIR)/rsa_tools/tomsfastmath-0.13.1
LOKISETUPDIR=misc/setup LOKISETUPDIR=misc/setup
NSISDIR=misc/nsis NSISDIR=misc/nsis
WEBDIR=$(MOUNT_DIR)/web
SDLHDIR=$(MOUNT_DIR)/SDL2 SDLHDIR=$(MOUNT_DIR)/SDL2
LIBSDIR=$(MOUNT_DIR)/libs LIBSDIR=$(MOUNT_DIR)/libs
@ -1069,8 +1070,6 @@ ifeq ($(PLATFORM),emscripten)
BUILD_RENDERER_OPENGL1=0 BUILD_RENDERER_OPENGL1=0
BUILD_SERVER=0 BUILD_SERVER=0
CLIENT_EXTRA_FILES+=code/web/ioquake3.html
CLIENT_CFLAGS+=-s USE_SDL=2 CLIENT_CFLAGS+=-s USE_SDL=2
CLIENT_LDFLAGS+=-s TOTAL_MEMORY=256MB CLIENT_LDFLAGS+=-s TOTAL_MEMORY=256MB
@ -1231,6 +1230,8 @@ ifeq ($(PLATFORM),emscripten)
endif endif
ifneq ($(BUILD_CLIENT),0) ifneq ($(BUILD_CLIENT),0)
TARGETS += $(B)/$(CLIENTBIN).html
ifneq ($(USE_RENDERER_DLOPEN),0) ifneq ($(USE_RENDERER_DLOPEN),0)
GENERATEDTARGETS += $(B)/$(CLIENTBIN).$(ARCH).wasm GENERATEDTARGETS += $(B)/$(CLIENTBIN).$(ARCH).wasm
ifeq ($(EMSCRIPTEN_PRELOAD_FILE),1) ifeq ($(EMSCRIPTEN_PRELOAD_FILE),1)
@ -3086,6 +3087,15 @@ $(B)/$(MISSIONPACK)/qcommon/%.asm: $(CMDIR)/%.c $(Q3LCC)
$(DO_Q3LCC_MISSIONPACK) $(DO_Q3LCC_MISSIONPACK)
#############################################################################
# EMSCRIPTEN
#############################################################################
$(B)/$(CLIENTBIN).html: $(WEBDIR)/client.html
$(echo_cmd) "SED $@"
$(Q)sed 's/__CLIENTBIN__/$(CLIENTBIN)/g;s/__BASEGAME__/$(BASEGAME)/g' < $< > $@
############################################################################# #############################################################################
# MISC # MISC
############################################################################# #############################################################################

View file

@ -102,16 +102,14 @@ For macOS, building a Universal Binary 2 (macOS 10.9+, arm64, x86_64)
For Web, building with Emscripten For Web, building with Emscripten
1. Follow the installation instructions for the Emscripten SDK including 1. Follow the installation instructions for the Emscripten SDK including
setting up the environment with emsdk_env. setting up the environment with emsdk_env.
2. Run `emmake make debug` (or release, but if you do both then you will 2. Run `emmake make debug` (or release).
need to pass an extra URL parameter to the HTML file to select the
build you want).
3. Copy or symlink your baseq3 pk3 files into the `build/debug-emscripten-wasm32/baseq3` 3. Copy or symlink your baseq3 pk3 files into the `build/debug-emscripten-wasm32/baseq3`
directory so they can be loaded at run-time. Only game files listed in directory so they can be loaded at run-time. Only game files listed in
`ioq3-config.json` will be loaded. `ioq3-config.json` will be loaded.
4. Start a web server serving this directory. `python3 -m http.server` 4. Start a web server serving this directory. `python3 -m http.server`
is an easy default that you may already have installed. is an easy default that you may already have installed.
5. Open `code/web/ioquake3.html` in a web browser. Open the developer 5. Open `http://localhost:8000/build/debug-emscripten-wasm32/ioquake3.html`
console to see errors and warnings. in a web browser. Open the developer console to see errors and warnings.
6. Debugging the C code is possible using a Chrome extension. For details 6. Debugging the C code is possible using a Chrome extension. For details
see https://developer.chrome.com/blog/wasm-debugging-2020 see https://developer.chrome.com/blog/wasm-debugging-2020

96
code/web/client.html Normal file
View file

@ -0,0 +1,96 @@
<!DOCTYPE html><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>__CLIENTBIN__ Emscripten demo</title>
<style>
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: rgb(0, 0, 0); display:flex; align-items: center; justify-content: center; }
canvas { max-width: 100%; max-height: 100%; min-width: 100%; min-height: 100%; object-fit: contain; }
</style>
<canvas id=canvas></canvas>
<script type=module>
// These strings are set in the generated HTML file in the build directory.
let CLIENTBIN = '__CLIENTBIN__';
let BASEGAME = '__BASEGAME__';
// Detect if it's not the generated HTML file.
let clientHtmlFallback = (CLIENTBIN === '\_\_CLIENTBIN\_\_');
// Path or URL containing the client engine .js, .wasm, and possibly .data.
let enginePath = './';
// Path or URL containing fs_game directories.
let dataPath = './';
// Path or URL for config file that specifies the files to load for each fs_game.
let configFilename = './ioq3-config.json';
// If displaying the unmodified HTML file, fallback to defaults.
if (clientHtmlFallback) {
CLIENTBIN='ioquake3';
BASEGAME='baseq3';
}
if (window.location.protocol === 'file:') throw new Error(`Unfortunately browser security restrictions prevent loading wasm from a file: URL. This file must be loaded from a web server. The easiest way to do this is probably to use Python\'s built-in web server by running \`python3 -m http.server\` in the top level source directory and then navigate to http://localhost:8000/build/debug-emscripten-wasm32/${CLIENTBIN}.html`);
// First set up the command line arguments and the Emscripten filesystem.
const urlParams = new URLSearchParams(window.location.search);
const basegame = urlParams.get('basegame') || BASEGAME;
let generatedArguments = `
+set sv_pure 0
+set net_enabled 0
+set r_mode -2
+set fs_game ${basegame}
`;
// Note that unfortunately "+" needs to be encoded as "%2b" in URL query strings or it will be stripped by the browser.
const queryArgs = urlParams.get('args');
if (queryArgs) generatedArguments += ` ${queryArgs} `;
// If displaying the unmodified HTML file, the engine and data are probably located in a different directory.
if (clientHtmlFallback) {
// If buildPath is not specified, try to find a build in one of a few default paths.
let buildPath = urlParams.get('buildPath');
if (buildPath && !buildPath.endsWith('/')) buildPath += '/';
const buildPaths = buildPath ? [buildPath] : ['../../build/debug-emscripten-wasm32/', '../../build/release-emscripten-wasm32/', './'];
const scriptPaths = buildPaths.map(buildPath => buildPath + `${CLIENTBIN}_opengl2.wasm32.js`);
const scriptResponses = await Promise.all(scriptPaths.map(p => fetch(p, {method: 'HEAD'})));
const validBuilds = scriptResponses.filter(r => r.ok).length;
const goodURL = (newPath) => {
const url = new URL(window.location);
url.searchParams.set('buildPath', newPath);
return url.toString().replace(/%2f/gi, '/');
};
if (validBuilds === 0) throw new Error(`Didn't find any wasm builds. Run \`emmake make debug\` to build one, or use the buildPath query parameter to specify a directory containing ${CLIENTBIN}_opengl2.wasm32.[js,wasm,data], e.g. ${goodURL('../../build/debug-emscripten-wasm32/')}`);
if (validBuilds > 1) throw new Error(`Found multiple valid builds at the following paths: [${buildPaths.filter((path, i)=>scriptResponses[i].ok)}]. Please specify which one to run by adding a buildPath query parameter to the URL, e.g. ${goodURL(buildPaths.filter((path, i)=>scriptResponses[i].ok)[0])}`);
const buildIndex = scriptResponses.findIndex(r => r.ok);
enginePath = buildPaths[buildIndex];
dataPath = buildPaths[buildIndex];
configFilename = dataPath + 'ioq3-config.json';
}
const dataURL = new URL(dataPath, location.origin + location.pathname);
const configPromise = fetch(configFilename).then(r => r.ok ? r.json() : {files: []});
const ioquake3 = (await import(enginePath + `${CLIENTBIN}_opengl2.wasm32.js`)).default;
ioquake3({
canvas: canvas,
arguments: generatedArguments.trim().split(/\s+/),
locateFile: (file) => enginePath + file,
preRun: [async (module) => {
module.addRunDependency('setup-ioq3-filesystem');
try {
const config = await configPromise;
const fetches = config.files.map(file => fetch(new URL(file.src, dataURL)));
for (let i = 0; i < config.files.length; i++) {
const response = await fetches[i];
if (!response.ok) continue;
const data = await response.arrayBuffer();
let name = config.files[i].src.match(/[^/]+$/)[0];
let dir = config.files[i].dst;
module.FS.mkdirTree(dir);
module.FS.writeFile(`${dir}/${name}`, new Uint8Array(data));
}
} finally {
module.removeRunDependency('setup-ioq3-filesystem');
}
}],
});
</script>

View file

@ -1,71 +0,0 @@
<!DOCTYPE html><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>ioquake3 Emscripten demo</title>
<style>
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: rgb(0, 0, 0); display:flex; align-items: center; justify-content: center; }
canvas { max-width: 100%; max-height: 100%; min-width: 100%; min-height: 100%; object-fit: contain; }
</style>
<canvas id=canvas></canvas>
<script type=module>
if (window.location.protocol === 'file:') throw new Error('Unfortunately browser security restrictions prevent loading wasm from a file: URL. This file must be loaded from a web server. The easiest way to do this is probably to use Python\'s built-in web server by running `python3 -m http.server` in the top level ioq3 directory and then navigating to http://localhost:8000/code/web/ioquake3.html');
// First set up the command line arguments and the Emscripten filesystem.
const urlParams = new URLSearchParams(window.location.search);
const basegame = urlParams.get('basegame') || 'baseq3';
let generatedArguments = `
+set sv_pure 0
+set net_enabled 0
+set r_mode -2
+set fs_game ${basegame}
`;
// Note that unfortunately "+" needs to be encoded as "%2b" in URL query strings or it will be stripped by the browser.
const queryArgs = urlParams.get('args');
if (queryArgs) generatedArguments += ` ${queryArgs} `;
// If buildPath is not specified, try to find a build in one of a few default paths.
let buildPath = urlParams.get('buildPath');
if (buildPath && !buildPath.endsWith('/')) buildPath += '/';
const buildPaths = buildPath ? [buildPath] : ['../../build/debug-emscripten-wasm32/', '../../build/release-emscripten-wasm32/', './'];
const scriptPaths = buildPaths.map(buildPath => buildPath + 'ioquake3_opengl2.wasm32.js');
const scriptResponses = await Promise.all(scriptPaths.map(p => fetch(p, {method: 'HEAD'})));
const validBuilds = scriptResponses.filter(r => r.ok).length;
const goodURL = (newPath) => {
const url = new URL(window.location);
url.searchParams.set('buildPath', newPath);
return url.toString().replace(/%2f/gi, '/');
};
if (validBuilds === 0) throw new Error(`Didn't find any wasm builds. Run \`emmake make debug\` to build one, or use the buildPath query parameter to specify a directory containing ioquake3_opengl2.wasm32.[js,wasm,data], e.g. ${goodURL('../../build/debug-emscripten-wasm32/')}`);
if (validBuilds > 1) throw new Error(`Found multiple valid builds at the following paths: [${buildPaths.filter((path, i)=>scriptResponses[i].ok)}]. Please specify which one to run by adding a buildPath query parameter to the URL, e.g. ${goodURL(buildPaths.filter((path, i)=>scriptResponses[i].ok)[0])}`);
const buildIndex = scriptResponses.findIndex(r => r.ok);
const selectedScript = scriptPaths[buildIndex];
buildPath = buildPaths[buildIndex];
const buildURL = new URL(buildPath, location.origin + location.pathname);
const configPromise = fetch(buildPath + 'ioq3-config.json').then(r => r.ok ? r.json() : {files: []});
const ioquake3 = (await import(selectedScript)).default;
ioquake3({
canvas: canvas,
arguments: generatedArguments.trim().split(/\s+/),
locateFile: (file) => buildPath + file,
preRun: [async (module) => {
module.addRunDependency('setup-ioq3-filesystem');
try {
const config = await configPromise;
const fetches = config.files.map(file => fetch(new URL(file.src, buildURL)));
for (let i = 0; i < config.files.length; i++) {
const response = await fetches[i];
if (!response.ok) continue;
const data = await response.arrayBuffer();
let name = config.files[i].src.match(/[^/]+$/)[0];
let dir = config.files[i].dst;
module.FS.mkdirTree(dir);
module.FS.writeFile(`${dir}/${name}`, new Uint8Array(data));
}
} finally {
module.removeRunDependency('setup-ioq3-filesystem');
}
}],
});
</script>