Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 129 additions & 5 deletions assets/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,57 @@
transform: rotate(360deg);
}
}

.bar-container {
width: 128px;
height: 12px;
border: 3px solid #ececec;
border-radius: 0.375em;
position: relative;
overflow: hidden;
margin-top: 24px;
margin-bottom: 12px;
}

.loading-bar {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #b2b2b2;
transform-origin: center left;
transform: scaleX(0);
}

.progress-text {
font-size: 0.9rem;
color: #ececec;
font-family:
DejaVu Sans Mono,
monospace;
}

.error {
font-size: 0.9rem;
color: salmon;
font-family:
DejaVu Sans Mono,
monospace;
margin-top: 12px;
}
</style>
</head>

<body class="center">
<noscript>JavaScript support is required to run this app</noscript>
<div id="loading-screen" class="center">
<span class="spinner"></span>
<div hidden="hidden" class="bar-container">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a fan of semantic HTML, which is also more accessible.
Can we use the <progress> element instead?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In most cases I would absolutely agree. However, styling the progress element is a little nasty between browsers. It would require a bit more CSS than this PR adds already.

Maybe we could compromise with a few additional aria attributes?

<div class="loading-bar"></div>
</div>
<div class="progress-text"></div>
<div class="error"></div>
</div>

<script type="module">
Expand Down Expand Up @@ -134,14 +178,94 @@
</script>

<script type="module">
// Starting the game

// When this file is used as the default `index.html`, the CLI will automatically replace
// `bevy_app.js` with the name of the generated JS entrypoint. If you copy this file and
// customize it, you will need to manually change the name. For more information, please see
// `bevy_app.js` and `bevy_app_bg.wasm` with the name of the generated JS entrypoint and Wasm
// binary. If you copy this file and customize it, you will need to manually change
// the names. For more information, please see
// <https://thebevyflock.github.io/bevy_cli/cli/web/default-index-html.html>!
import init from "./build/bevy_app.js";
init().catch((error) => {
const wasmPath = "./build/bevy_app_bg.wasm";
const wasmSize = null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading this by itself is quite confusing. I wonder if we need a more visual indicator for the template replacement, especially so that users can use it for their custom index files.

But that issue existed before, so it probably makes sense to tackle it in a different PR

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The three calls to replace are also rather ugly in the Rust code.


// Provide progress reporting when fetching the Wasm binary
async function fetchWithProgress(url, total, onProgress) {
const response = await fetch(url);

if (!response.ok) {
throw new Error(`request failed with status ${response.status}`);
}

if (total === null) {
// If the content length isn't provided,
// we'll just skip the progress reporting
return await response.arrayBuffer();
}

let receivedLength = 0;
const chunks = [];

for await (const chunk of response.body) {
chunks.push(chunk);
receivedLength += chunk.length;

const progress = receivedLength / total;

// Interacting with the DOM or other operations
// here should not cause downloading to fail
try {
onProgress({
loaded: receivedLength,
total: total,
progress: progress,
});
} catch (e) {
console.error(e);
}
}

const chunksAll = new Uint8Array(receivedLength);
let position = 0;
for (let chunk of chunks) {
chunksAll.set(chunk, position);
position += chunk.length;
}

return chunksAll.buffer;
}

function humanReadable(number) {
// Working with Bevy on the web, we can safely
// assume this scale
return `${(number / 1e6).toFixed(2)} MB`;
}

async function loadWasm(url, size) {
let arrayBuffer;
try {
arrayBuffer = await fetchWithProgress(url, size, (stats) => {
const container = document.querySelector(".bar-container");
container.removeAttribute("hidden");

const bar = document.querySelector(".loading-bar");
bar.style.transform = `scaleX(${stats.progress})`;

const text = document.querySelector(".progress-text");
text.innerText = humanReadable(stats.loaded);
});
} catch (e) {
// Here, we communicate any request failure to the user
const errorNode = document.querySelector(".error");
if (errorNode !== null) {
errorNode.innerText = e.toString();
}
throw e;
}

return await init(arrayBuffer);
}

// Starting the game
loadWasm(wasmPath, wasmSize).catch((error) => {
if (
!error.message.startsWith(
"Using exceptions for control flow, don't mind me. This isn't actually an error!"
Expand Down
22 changes: 20 additions & 2 deletions src/web/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,28 @@ fn default_index(bin_target: &BinTarget) -> String {
));

// Insert correct path to JS bindings
template.replace(
let template = template.replace(
"./build/bevy_app.js",
format!("./build/{}.js", bin_target.bin_name).as_str(),
)
);
let template = template.replace(
"./build/bevy_app_bg.wasm",
format!("./build/{}_bg.wasm", bin_target.bin_name).as_str(),
);

if let Ok(metadata) = std::fs::metadata(
bin_target
.artifact_directory
.join(format!("{}_bg.wasm", bin_target.bin_name)),
) {
let length = metadata.len();
template.replace(
"const wasmSize = null;",
format!("const wasmSize = {}", length).as_str(),
)
} else {
template
}
}

/// Generate a title to display on the web page by default.
Expand Down