Skip to main content

7 posts tagged with "rust"

View All Tags

More voxels

· One min read

Working a bit on terrain generation. Turns out it can be a lot more fun working on this in native desktop with Rust rather than WebGL given the runtime limitations of the latter.

alt text

alt text

alt text

alt text

Bevy voxels

· One min read

Still working on voxel rendering in Bevy. Restarted from fresh as I've learned a bit more since last time...

alt text

alt text

alt text

Voxelizer

· One min read

Been working on a lot of different experiments lately. Merging my recent interest in learning Bevy, Rust, and WASM with my long-time interest in voxel rendering, I've been working on an experimental program to convert a Quake 2 BSP38 map file into voxelized representation.

The above is slow and inefficient, but it's an interesting starting point as it's all running in WASM using Bevy.

Bevy WASM rendering

· 5 min read
A Bevy "lava lamp"

Added an experiment of rendering with Bevy using WASM. It's nothing too exciting as I'm new to Bevy, WASM, and still relatively inexperienced with Rust!

Demo page here.

A few notes on the development:

Letting the browser decide the host Element ID

I wanted to host this experiment (and future ones) within the context of pages within the Docusaurus app. I certainly do not know the "best" way to do this yet, but one definite constraint to pass the canvas id to the WASM module at startup rather than hard-coding it in the WASM and browser.

As far as I can tell, the WASM init function does not take arguments, therefore the startup is exposed via a separate wasm_bindgen exported function called start.

In the Rust snippet below, you can see we do nothing in main and explicitly pass in an id to the WindowPlugin in a separate start function.

fn main() {
// The run() entrypoint does the work as it can be called
// from the browser with parameters.
}

#[wasm_bindgen]
pub fn start(canvas_id: &str) {
let id = format!("#{}", canvas_id);

App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
canvas: Some(id.into()),
..default()
}),
..default()
}))
.add_systems(Startup, setup)
.add_systems(
Update,
(
move_ball, //
update_transforms,
),
)
.run();
}

The JavaScript code to bootstrap this looks like this:

const go = async () => {
let mod = await import(moduleName);
await mod.default();
await mod.start(canvasID);
};
go();

But admittedly the above is not the actual JavaScript code for hosting the WASM module...

Running the WASM code in Docusaurus

Disclaimer

This is workaround code. It "works" but I'm sure there is a correct way to handle this that I was not able to discover!

(1) Idempotence

There's something I don't understand about WASM module loading and, more importantly, reloading/reuse. This is problematic in the context of a Single Page Application (SPA) like Docusaurus where if you navigate to page ABC, then to page XYZ, then back to ABC, any initialization that happened on the first visit to page ABC will happen again on the second visit. In other words, I'm not sure how to make the WASM initalization idempotent.

If there's a correct way to...

  • Reuse a WASM module that's already loaded
  • Unload a WASM module on navigation away from the page
  • Reload a WASM module

...I'd enjoy learning how!

(2) Docusaurus script building/bundling

Docusaurus also has logic for renaming, bundling, rewriting, etc. JavaScript code used on the pages. I'm not sure what the exact logic of what it does, but end of the day, I did not want Docusaurus manipulating the JS generated by the WASM build process.

Admittedly this is a bit of laziness on my part for not really understanding what Docusaurus does and how best to circumvent it.

Workaround

I worked around the reload problems and script mangling with a custom MDX Component in Docusaurus that:

  1. Directly injects a <script> element so Docusaurus can't modify what it does
  2. Uses a hacky exception handler to reload the page if the WASM seems to fail (usually what happens on the second visit to the page without an intermittent reload)
  3. Sets a retry limit in case the logic for (2) is not right in all cases (because this is a hacky workaround, not robust code so precautions against an infinite loop are important!)
export function CanvasWASM({
id,
module,
width,
height,
style,
}: {
id: string,
module: string,
width: number,
height: number,
style?: React.CSSProperties,
}) {
React.useEffect(() => {
// We create a a DOM element since Docusaurus ends up renaming /
// changing the JS file to load the WASM which breaks the import
// in production. This is pretty hacky but it works (for now).
const script = document.createElement('script');
script.type = 'module';
script.text = `
let key = 'wasm-retry-count-${module}-${id}';
let success = setTimeout(function() {
localStorage.setItem(key, "0");
}, 3500);

const go = async () => {
try {
let mod = await import('${module}');
await mod.default();
await mod.start('${id}');
localStorage.setItem(key, "0");
} catch (e) {
if (e.message == "unreachable") {
clearTimeout(success);
let value = parseInt(localStorage.getItem(key) || "0", 10);
if (value < 10) {
console.log("WASM error, retry attempt: ", value);
setTimeout(function() {
localStorage.setItem(key, (value + 1).toString());
window.location.reload();
}, 20 + 100 * value);
} else {
throw e;
}
}
}
};
go();
`.trim();
document.body.appendChild(script);
}, []);

return <canvas id={id} width={width} height={height} style={style}></canvas>;
}

An aside of GitHub Large File Storage (LFS)

In case anyone on the internet runs into this and stumbles upon this page...

I also wasted quite a bit of time on this problem:

  1. I publish this site using GitHub Pages
  2. I generally set a .gitattributes file in all my repos to store generated files using git LFS
  3. I have an entry for *.wasm to store those in LFS
  4. GitHub Pages doesn't use LFS when serving the files

This meant my code was working locally but when I was trying to load the WASM files on the published site, the LFS "pointer" text file was being served rather than the binary WASM file itself. It took me a while to figure this out. Ultimately the fix was to remove the .gitattributes from the GitHub Pages repo so LFS is not used on the published site. (Aside: this might be a good reason to consider hosting this site via another platform, but I'll leave that for another day!)

Terrain generator

· One min read

Added the ability to write Rust "scripts" (basically crates that the engine will run to get model output from) and added a voxel terrain with some basic procedural noise. I think of it as a "script" since it uses a blanket important use scriptlib::*; which brings in a lot of utilities to keep the code itself concise.

Also switched the WGPU to prefer linear RGB color until I progress a bit further and want to handle sRGB correctly.

alt text

Rust generator "scripts"

· One min read

Updated the code base to run Rust code to generate models. The below heightmap is a combination of sin functions and noise functions, generated in Rust code.

alt text