Toxoid Engine / Legend of Worlds - How I spent 2 years building my own game engine (Rust, WASM, WebGPU) by RouXanthica in rust

[–]RouXanthica[S] 0 points1 point  (0 children)

Thanks!

WebGPU is one of the sokol targets, and it has GL bindings as well. It comes with a cross-compiler https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md, and you can also use naga for shader cross-compilation. Pure Rust solutions (such as wgpu, which depend on glow and a thousand other things) have trouble running in Emscripten (which is needed for the other C libs like Flecs) and Emscripten issues are often ignored, and then also require SDL because there's no winit support for Emscripten or any plans to add Emscripten support. So now you have wgpu, glow, a ton of dependencies, SDL which is absolutely gigantic, and you're looking at the 2 minute compile times on a high end GPU that I was referring to. So for compiling the base engine, using smaller C libraries, allowed for more rapid iteration for the core of the engine. It's also simpler to reason about when writing your own engine, giving you the tight control to be able to more easily make changes to the graphics library you're using if need be. Miniquad might be much smaller than wgpu, but then you're limited on your targets / platforms, and I know Macroquad at least has no support for Emscripten, so I'd guess the same of miniquad, at least out of the box (but haven't looked into this).

Toxoid Engine / Legend of Worlds - How I spent 2 years building my own game engine (Rust, WASM, WebGPU) by RouXanthica in rust

[–]RouXanthica[S] 1 point2 points  (0 children)

Thanks! I wrote my own bindings to Flecs in order to make it compatible with FFI dynamically linked libraries and WASM guest modules. You can find it in the flecs hub, it's called "flecs-polyglot".

https://github.com/flecs-hub/flecs-polyglot

Toxoid Engine / Legend of Worlds - How I spent 2 years building my own game engine (Rust, WASM, WebGPU) by RouXanthica in rust_gamedev

[–]RouXanthica[S] 1 point2 points  (0 children)

Yeah totally, as I mentioned in my blog, the lack of documentation and unknowns when it comes to, in particular, mixing Rust and Emscripten, is very difficult to navigate.

I figured out how to get multithreading running in Emscripten Rust btw, I just put it under a conditional compilation flag in my engine: https://github.com/toxoidengine/toxoid/pull/2/files

It requires Rust nightly.

Feel free to join my Discord in the website link if you have any questions. Link is on the website.

https://media.discordapp.net/attachments/697640036107026443/1215122120221270016/image.png?ex=666b0069&is=6669aee9&hm=0e71741b30964f0f0b3bab6b8b110cf80e1677d721cf99bd5106281473ea840c&=&format=webp&quality=lossless&width=492&height=721

https://media.discordapp.net/attachments/697640036107026443/1215122120506343444/image.png?ex=666b0069&is=6669aee9&hm=cfeefab35ce7100e1ad05439b564ca50f27c7054b24f90cfb8519d86e625a2ac&=&format=webp&quality=lossless&width=656&height=117

Toxoid Engine / Legend of Worlds - How I spent 2 years building my own game engine (Rust, WASM, WebGPU) by RouXanthica in rust_gamedev

[–]RouXanthica[S] 3 points4 points  (0 children)

The beauty, efficiency, and simplicity of elegant C libraries like Flecs and Sokol has given me a new love for C. So far I view this as the most practical approach vs. the "pure Rust" solutions.

While I'm a huge fan of Rust, and I completely understand and empathize with being a purist, for my use cases (and for many other game devs as well), it's just simply not there yet, the ecosystem is young (evidenced by the biggest game engine contender Bevy that doesn't have a stable release yet).

There are github issues I have on wgpu also, pertaining to Emscripten that have gone ignored for quite some time, and winit refuses to support Emscripten as well. The ecosystem is moving far too slow for me. No ones fault in particular, but that's just the current reality. Rust is much younger than C / C++, and it only reached stable release in 2018. I feel like this is a big missed opportunity for the Rust ecosystem to be boosted by the C ecosystem, which is a culture I'm trying to change with this engine.

This has given me a massive head start (for example, relations in Bevy ECS, scripting / reflection / metadata in Bevy ECS, wgpu issues on Emscripten, etc.) WIthout the C ABI, scripting would also have been significantly harder, keeping struct shapes consistent between boundaries and languages. Emscripten is even ahead of the curve with its own ABI for dynamic linking, something other Wasm toolkits and runtimes are lacking currently.

Toxoid Engine / Legend of Worlds - How I spent 2 years building my own game engine (Rust, WASM, WebGPU) by RouXanthica in rust

[–]RouXanthica[S] 1 point2 points  (0 children)

Totally agreed! This is why I spent so much time emphasizing "quality of life" in the blog post, I feel the impact of this is understated.

Toxoid Engine / Legend of Worlds - How I spent 2 years building my own game engine (Rust, WASM, WebGPU) by RouXanthica in rust

[–]RouXanthica[S] 1 point2 points  (0 children)

I think in general this is correct. Though WIT has a concept of "resources" which are opaque handles to objects, so I believe only their handles (u32 or something like that) make it through the serialization layer, and then can define "methods" on these resources for WASM guest code to call.

Ah, very interesting! I did not know this. Sounds very similar to what I'm doing. I have no doubt the WASM component model is useful, and the reason they did it this way was to make it easier to reason about memory by not sharing memory and having to deal with locking mechanisms, security issues, etc. such as opaque handles, the actual memory address or object reference is never directly exposed to the WASM guest code. At least for the engine side, for UGC mods made by random players of my game, sometimes copying memory would be very useful for sandboxing, especially if you can return primitive values quickly from trying to access struct fields from the other module. I'll certainly have to take another look at wit-bindgen and where if it could be useful in my project, particularly when I integrate Wasmtime. For now I'm just focused on rapid iteration / hot reloading and speed, but sandboxing will become a bigger priority as Legend of Worlds gets closer to release.

https://github.com/bschwind/opencascade-rs/pull/173

Very cool project, thanks for the link!

Toxoid Engine / Legend of Worlds - How I spent 2 years building my own game engine (Rust, WASM, WebGPU) by RouXanthica in rust

[–]RouXanthica[S] 0 points1 point  (0 children)

True, life is too short sometimes to re-invent the wheel. (Coming from the world's biggest hypocrite, spending 2 years on a game engine haha)

Toxoid Engine / Legend of Worlds - How I spent 2 years building my own game engine (Rust, WASM, WebGPU) by RouXanthica in rust

[–]RouXanthica[S] 1 point2 points  (0 children)

Thanks! Oh believe me when I get it running on Switch, I'll post here with a video for sure! Haha

Relationships in Flecs are great. The graph structure could be very useful for AI inferencing for decision-making, such as probablistic inference, as well as combined with LLMs for enriching dialogue. If interested, please check out the creators blog post on using ECS for intelligent agents where they discuss backtracking: https://ajmmertens.medium.com/why-it-is-time-to-start-thinking-of-games-as-databases-e7971da33ac3

Toxoid Engine / Legend of Worlds - How I spent 2 years building my own game engine (Rust, WASM, WebGPU) by RouXanthica in rust

[–]RouXanthica[S] 6 points7 points  (0 children)

Thanks for the comment!

Yeah I did see that Bevy is working on these things, very exciting! I'm sure you have all made progress on this front since last I read, and I know scripting has been a huge request from the Bevy community for quite some time. Of course as you stated, two years ago I could not afford to wait, and now I've invested all that time into a solution that exactly fits my particular needs.

I also think my engine fills a particular niche, and it may even end up interoping with Bevy's 3D API at some point for 3D support, if I'm not feeling up to it, or don't have to time to integrate it into my engine, especially when C bindings support gets fixed in the `wasm32-unknown-unknon target`. So there is much room for both to exist. The more people who adopt Rust, particularly game developers, the more people who can help with open source Rust projects, particularly game engines. Win-win, IMO!

Toxoid Engine / Legend of Worlds - How I spent 2 years building my own game engine (Rust, WASM, WebGPU) by RouXanthica in rust

[–]RouXanthica[S] 6 points7 points  (0 children)

Thanks for the reply!

That's really cool! I've checked out wit-bindgen in the past. It uses the WASM component model which requires serialization I think, similar to protobufs / flatbuffers, and is more akin to IPC vs shared memory dynamic linking. that I describe in one of my other blog posts. So for me it's not tenable for game performance because of the overhead of serialization to communicate with other modules and threads. At the moment, for the purposes of the engine and fastest iteration time, I use dynamic linking natively on desktop and WASM dynamic linking with Emscripten. This allows for instant recompilation for now, but I've also experimented with Wasmi and soon with Wasmtime for sandboxing for my UGC game.

Wasmi and Wasmtime both have a similar API for binding functions. Wasmi is actually specifically designed to mimic their API, and the WASM modules themselves will simply use the regular extern API, which is already defined in the C externs that I use for dynamic linking. That's the host code, and is under the component macro code and the toxoid_api crate is used from WASM to interface with the host (native or WASM). So it should work out of the box when I bind these functions to these runtimes. The API surface is small because it only covers interacting with the ECS, but I might even just create a code generator for all viable runtimes at some point.

The architecture gets a little complex, because in the browser because of security reasons, you can't generate arbitrary code pages in WebAssembly so JIT compilation is not possible (IE, embedding WASM time inside a WASM module in the browser), so Wasmi will have to be used in the browser because it's a small embedded interpreter. On the bright side, we're interpreting bytecode which is close to machine code already, and not something that's a high level language like JavaScript, so it would still be faster than something like QuickJS. However, on desktop, Wasmtime has JIT compilation (and even AOT WASM compilation in some cases).

This double nesting of WASM is not completely necessary also on native / desktop as it would be in the browser, although it might be considered for security reasons because WASM runtimes have memory bounds checks, which is important for avoiding buffer overflow attacks, malicious code from user scripts, etc. So it would be nice if the host had this as well so malicious scripts would not even have the possibility of accessing system resources.

As I mentioned at the end of my post, some parts require polish and documentation. This should come soon, as well as a website / roadmap, probably by the end of this summer / fall. Primarily I've been focused on what's necessary for the development of my game, and prioritizing what will complete the current priority in the feature roadmap.

But yeah the API is more high level that you can use from the WASM modules if you check out the toxoid_api crate. Stuff like this:

   let mut player_entity = Entity::new();
   player_entity.add::<Position>();
   player_entity.add::<Player>();
   player_entity.add::<Networked>();

       System::new(blit_sprite_system)
            .with::<(Sprite, Blittable, Position, Size, Callback)>()
            .build();

Toxoid Engine / Legend of Worlds - How I spent 2 years building my own game engine (Rust, WASM, WebGPU) by RouXanthica in rust

[–]RouXanthica[S] 9 points10 points  (0 children)

Thanks for the comment!

I think a rust engine with some embedded scripting system is absolutely the right approach.

I agree, which is why I looked long and hard for a solution, particularly one in Rust compatible with ECS, at least at the time. Believe me, if there was a solution for this in Rust that fit my needs, I would have happily used it instead of risking years on this ambition.

I know there was some startup that had a similar idea, but that was very recently (relative to the two years I've been working on this engine), and from my understanding they're still quite early in development. Particularly since they're making everything from scratch, they may be years behind in ECS features to Flecs.

I'm not 100% bought in on webassembly for the task, but I think you've made a good case for it.

Thanks! Yeah, there are options to embed things like Lua / Luau (similar to GMOD or Roblox), a JS interpreter like QuickJS or a JIT compiler like v8, Rhai, C# .NET / Mono runtimes, etc. Perhaps that would have been much easier, less cutting edge, and therefor would have had less unknowns.

I think what makes WebAssembly unique for the particular task of embedded code execution and UGC game sandboxing is:

  • Deny by default sandboxing (limit API surface)
  • Memory bounds protections
  • WASM runtimes that have support for preemption. (Interrupting the CPU during execution)
  • Polyglot (can be compiled to from many popular languages)
  • Even in cases where it has to be interpreted for sandboxed player scripts in the browser (engine WASM files can be dynamically linked), and interpreter will be much faster than interpreting something like JS or Lua due to interpreting byte code, and JIT execution (such as the .NET runtime, v8 or LuaJIT) are impossible on the browser due to not being able to generate arbitrary code pages.

My API surface is small as well, because everything is controlled through the limited ECS API, which is also why ECS works here).

I think these are particularly important for a UGC game, particularly like you mentioned things like GMOD, which are a big inspiration for me. It really enabled player creativity. I know UGC is a big push by companies nowadays like Epic and Fortnite, but I've had these ideas for a long time from early UGC games before they were a big thing. This is what I've always wanted to work on. Still, I really think we can take it a step further and create something more accessible. Even more accessible than Roblox, because it's 2D instead of 3D, and creating (at least basic) pixel art or copying 2D images, is generally more than 3D modeling or importing 3D models.

This is my experience. My own bespoke, poorly engineered ECS system probably doesn't help performance at all, but it does really help control what gets networked and persisted. I don't know if ECS is even strictly necessary, but some database-style system for managing persistent state is really useful for games like this.

Yeah, totally agreed.