new web interface and loading progress

This commit is contained in:
ading2210 2024-05-31 17:37:57 -04:00
parent b8a53b9c9d
commit bc45f6027e
4 changed files with 117 additions and 43 deletions

View File

@ -1,9 +0,0 @@
/* DELETE
;run\(\);
*/
/* DELETE
if ?\(!calledRun\)\s*?run\(\);
*/

View File

@ -1,35 +1,97 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>SuperTuxKart</title>
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<script src="/game/supertuxkart.js"></script>
<script>
//emscripten only tells us if the runtime is ready once it starts main
//so cancel running main the first time around so we can store when it is ready
globalThis.ready = false;
var real_run = run;
run = () => {
if (globalThis.ready) {
real_run();
}
globalThis.ready = true;
}
</script>
<script src="/script.js" type="module" defer></script>
<style>
* {
font-family: Arial, sans-serif;
box-sizing: border-box;
}
p {
font-size: 12px;
margin: 4px;
}
body {
margin: 0px;
}
#canvas {
background-color: #222222;
position: absolute;
top: 0px;
left: 0px;
z-index: 1;
width: 100vw;
height: 100vh;
}
#info_container {
position: absolute;
top: 50%;
left: 50%;
width: 440px;
height: 260px;
margin-left: -220px;
margin-top: -130px;
z-index: 2;
background-color: white;
padding: 12px;
padding-top: 6px;
border-radius: 8px;
}
#header_bar {
display: flex;
align-items: center;
gap: 18px;
}
#status_bar {
display: flex;
align-items: flex-start;
align-items: center;
margin-top: 12px;
gap: 6px;
}
#start_button {
border: none;
outline: none;
padding: 8px;
padding-left: 12px;
padding-right: 12px;
border-radius: 8px;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<br>
<div id="status_bar">
<button id="start_button" disabled>Start Game</button>
<p id="status_text">Downloading game files...</p>
<div id="info_container">
<div id="header_bar">
<img src="/supertuxkart_64.png">
<h1 style="margin: 0px;">SuperTuxKart Wasm</h1>
</div>
<p>This is an experimental port of SuperTuxKart to the browser using WebAssembly and Emscripten. Currently, everything works except for some rendering APIs and networking.</p>
<p>There is a 700MB initial download required to play, and the game will require around 1GB of memory.</p>
<p>This web port was made by <a href="https://github.com/ading2210/">ading2210</a>, and the source code is located at <a href="https://github.com/ading2210/stk-code/tree/wasm">github.com/ading2210/stk-code</a>.</p>
<p>If you want to check on the status of this port, take a look at the <a href="https://github.com/supertuxkart/stk-code/pull/5106/">corresponding pull request</a> in the SuperTuxKart repository.</p>
<div id="status_bar">
<button id="start_button" disabled>Start Game</button>
<p id="status_text"></p>
</div>
</div>
<p>This web port was made by <a href="https://github.com/ading2210/">ading2210</a>.</p>
<p>The source code is located at <a href="https://github.com/ading2210/stk-code/tree/wasm">github.com/ading2210/stk-code</a>.</p>
</body>
</html>

View File

@ -8,6 +8,7 @@ let idbfs_mount = null;
let start_button = document.getElementById("start_button");
let status_text = document.getElementById("status_text");
let info_container = document.getElementById("info_container");
function load_db() {
if (db) return db;
@ -103,24 +104,33 @@ async function write_db_chunks(key, data) {
}
async function download_chunks(url) {
let path = url.split("/");
let filename = path.pop();
let base_url = path.join("/");
status_text.textContent = `Downloading manifest for ${filename}...`;
let r1 = await fetch(url + ".manifest");
let manifest = (await r1.text()).split("\n");
let size = parseInt(manifest.shift());
manifest.pop();
let path = url.split("/");
path.pop();
let base_url = path.join("/");
let offset = 0;
let chunk = null;
let array = new Uint8Array(size);
let chunk_count = manifest.length;
let current_chunk = 1;
while (chunk = manifest.shift()) {
let mb_progress = Math.floor(offset / (1024 ** 2))
let mb_total = Math.floor(size / (1024 ** 2))
status_text.textContent = `Downloading ${filename}... (chunk ${current_chunk}/${chunk_count}, ${mb_progress}/${mb_total}MiB)`;
let r2 = await fetch(base_url + "/" + chunk);
let buffer = await r2.arrayBuffer();
let chunk_array = new Uint8Array(buffer);
array.set(chunk_array, offset);
offset += chunk_array.length;
offset += chunk_array.length;
current_chunk++;
}
return array.buffer;
@ -130,11 +140,12 @@ async function extract_tar(url, fs_path, use_cache = false) {
//download tar file from server and decompress
let decompressed;
if (!use_cache || !await check_db(url)) {
let filename = url.split("/").pop();
let compressed = await download_chunks(url);
decompressed = pako.inflate(compressed);
compressed = null;
if (use_cache) {
console.log("saving to cache");
status_text.textContent = `Saving ${filename} to the cache...`;
await write_db_chunks(url, decompressed);
}
}
@ -162,11 +173,8 @@ async function extract_tar(url, fs_path, use_cache = false) {
}
async function load_data() {
console.log("downloading and extracting game data");
await extract_tar("/game/data.tar.gz", "/data", true);
console.log("downloading and extracting assets");
await extract_tar("/game/assets.tar.gz", "/data", true);
console.log("done")
}
async function load_idbfs() {
@ -183,28 +191,41 @@ function sync_idbfs(populate = false) {
})
}
function wait_for_frame() {
return new Promise((resolve) => {requestAnimationFrame(resolve)});
}
async function main() {
await load_data();
globalThis.ready = true;
await load_idbfs();
start_button.onclick = () => {requestAnimationFrame(start_game)};
start_button.onclick = start_game;
start_button.disabled = false;
status_text.textContent = "Ready";
}
function start_game() {
async function start_game() {
status_text.textContent = "Loading game files...";
start_button.disabled = true;
status_text.textContent = "Initializing";
requestAnimationFrame(() => {
run();
status_text.textContent = "Running";
sync_idbfs();
});
}
await load_data();
await wait_for_frame();
status_text.textContent = "Launching game...";
globalThis.pako = pako;
globalThis.jsUntar = jsUntar;
globalThis.load_data = load_data;
globalThis.sync_idbfs = sync_idbfs;
await wait_for_frame();
run();
info_container.style.zIndex = 0;
info_container.style.display = "none";
sync_idbfs();
console.warn("Warning: Opening devtools may harm the game's performance.");
}
Module["canvas"] = document.getElementById("canvas")
main();
globalThis.main = main;
globalThis.sync_idbfs = sync_idbfs;
globalThis.load_idbfs = load_idbfs;
let poll_runtime_interval = setInterval(() => {
if (globalThis.ready) {
main();
clearInterval(poll_runtime_interval);
}
}, 100);

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB