Experience of Developing Cross-Platform Games with Rust

Experience of Developing Cross-Platform Games with Rust

1. Introduction

Since being captivated by the MOD magic of Warcraft III in my childhood, I have always had a special affection for game scripting languages.

Looking back, developing game levels for Warcraft III using Blizzard’s JASS language, although JASS seems extremely rudimentary from today’s perspective, characterized by static typing and no garbage collection, it represented a bold attempt at game development languages during an era when industry standards had not yet formed.

Why Use Scripting Languages for Game Development?

The introduction of game scripting languages mainly aims to improve the convenience of development and testing. If a low-level language such as C++ is used directly, modifying a single line of code may require significant time waiting for the compilation and packaging of a complex toolchain. However, by using scripting languages, the programs that implement game mechanics can be executed with hot reloading, significantly enhancing development efficiency.

Over time, dynamic typing scripting languages like Lua and JavaScript have become common in game development.

However, with the evolution of programming languages, we have the opportunity to redefine a new standard for game scripting languages—both retro and innovative, which is the combination of Rust and WASM.

2. Rust + WASM + Dora SSR: Redefining Game Script Development

By combining Rust and WASM, we can perform game hot updates and testing directly on devices such as Android or iOS without sacrificing performance and without relying on traditional application development toolchains.

Moreover, with the Web IDE interface of the open-source Dora SSR game engine, game code written in Rust can be tested and run on various gaming devices after a single compilation.

Why Choose Rust?

Rust provides unparalleled memory safety guarantees and does not require the intervention of a garbage collector (GC), making it highly suitable for game development, especially in performance-sensitive scenarios. Combined with WASM, Rust can provide high-performance execution efficiency while maintaining cross-platform consistency and safety.

Quick Start Guide

Before we start development, we need to install the Dora SSR game engine. This engine supports multiple platforms, including Windows, Linux, macOS, iOS, and Android.

For specific installation steps and requirements, please refer to the official quick start guide: Dora SSR Quick Starthttps://dora-ssr.net/zh-Hans/docs/tutorial/quick-start/.

Experience of Developing Cross-Platform Games with Rust

Step 1: Create a New Project

After starting the binary of the Dora SSR engine, open Dora SSR’s Web IDE in a browser, right-click on the left game resource tree, select ‘New’, and create a new folder named ‘Hello’.

Experience of Developing Cross-Platform Games with Rust

Step 2: Write Game Code

Then create a new Rust project in the command line:

rustup target add wasm32-wasi
cargo new hello-dora --name init
cd hello-dora
cargo add dora_ssr
Write code in src/main.rs:
use dora_ssr::*;

fn main () {
  let mut sprite = match Sprite::with_file("Image/logo.png") {
    Some(sprite) => sprite,
    None => return,
  };
  let mut sprite_clone = sprite.clone();
  sprite.schedule(once(move |mut co| async move {
    for i in (1..=3).rev() {
      p!("{}", i);
      sleep!(co, 1.0);
    }
    p!("Hello World");
    sprite_clone.perform_def(ActionDef::sequence(&vec![
      ActionDef::scale(0.1, 1.0, 0.5, EaseType::Linear),
      ActionDef::scale(0.5, 0.5, 1.0, EaseType::OutBack),
    ]));
  }));
}
Build to generate the WASM file:
cargo build --release --target wasm32-wasi

Step 3: Upload and Run the Game

In the Dora SSR Web IDE, right-click the newly created folder ‘Hello’, select ‘Upload’, and upload the compiled WASM file init.wasm.
Experience of Developing Cross-Platform Games with Rust
Alternatively, use the auxiliary script upload.py to upload the WASM file within the Rust project folder, with the command below, where the IP parameter is the Web IDE address displayed after starting Dora SSR, and the latter parameter is the relative path of the directory to upload:
python3 upload.py "192.168.3.1" "Hello"
Experience of Developing Cross-Platform Games with Rust

Step 4: Publish the Game

In the editor’s left game resource tree, right-click the newly created project folder and select ‘Download’.

Wait for the browser to pop up a download prompt for the packaged project files.

3. How It Was Achieved

Implementing Rust language development support and embedding the WASM runtime in Dora SSR is a new technical exploration and attempt, mainly involving three key steps:

1. Designing the Interface Definition Language (IDL)

To embed the WASM runtime on a game engine written in C++ and support the Rust language, it is first necessary to design an interface definition language (IDL) to facilitate communication and data exchange between different programming languages.

Below is an example of a WASM IDL designed by Dora SSR, based on the program interface of the source language C++, with additional tags required for conversion to the Rust interface, such as object, readonly, optional, etc.

A challenge in cross-language interface mapping is that C++ interface design is object-oriented, while Rust does not provide complete object-oriented design capabilities, so some object-oriented interfaces need to be simulated in Rust with additional code. Fortunately, the language differences are not particularly large, and they can be resolved without complex mechanism designs.

object class EntityGroup @ Group
{
	readonly common int count;
	optional readonly common Entity* first;
	optional Entity* find(function<bool(Entity* e)> func) const;
	static EntityGroup* create(VecStr components);
};

2. Writing the Program to Generate Glue Code

The second step is to write a program that generates glue code for mutual invocation between C++, WASM, and Rust through the IDL.

To achieve this, we chose to use Yuescript, a language created by the Dora SSR project. Yuescript is a dynamic programming language based on Lua, combined with the lpeg syntax parsing library from the Lua language ecosystem to handle IDL parsing and glue code generation.

The benefit of using Yuescript is that it inherits the flexibility and lightweight nature of Lua while providing richer syntax and functionalities, suitable for handling complex code generation tasks.

Below is an excerpt of code for an IDL parser written using PEG grammar.

Param = P {
	"Param"
	Param: V"Func" * White * Name / mark"callback" + Type * White * Name / mark"variable"
	Func: Ct P"function<" * White * Type * White * Ct P"(" * White * (V"Param" * (White * P"," * White * V"Param")^0 * White)^-1 * P")" * White * P">"
}

Method = Docs * Ct(White * MethodLabel) * White * Type * White * (C(P"operator==") + Name) * White * (P"@" * White * Name + Cc false) * White * Ct(P"(" * White * (Param * (White * P"," * White * Param)^0 * White)^-1 * P")") * White * C(P"const")^-1 * White * P";" / mark"method"

3. Embedding the WASM Runtime and Code Integration

The final step is embedding the WASM runtime and the generated C++ glue code into the game engine, completing the code integration. For the WASM runtime, we chose to use WASM3, a high-performance, lightweight WebAssembly interpreter that supports multiple CPU architectures, simplifying the complexity of the compilation chain and enhancing cross-platform compatibility. Through this method, Dora SSR can support running games developed in Rust on various hardware devices, greatly improving the accessibility and flexibility of game projects.

During the integration process, we released a crate package for Rust developers, containing all necessary interfaces and tools, allowing developers to easily develop and republish other game modules written in Rust based on the Dora SSR game engine in the future.

4. Performance Comparison

The Dora SSR game engine also provides support for the Lua scripting language. Currently, it uses version 5.5 of the Lua virtual machine, which, like WASM3, does not perform JIT real-time machine code generation but interprets script code in the virtual machine. Therefore, we can make some performance comparisons between these two similar scripting solutions.

Before the comparison, we can roughly judge that, not considering the time consumed by Lua’s garbage collection, due to the dynamic nature of the Lua language, the program interfaces mapped from C++ to Lua often require runtime checks of parameter types during invocation, and the lookup of member attributes of Lua objects also needs to be done at runtime through a hash structure table. These are overheads that the statically typed Rust language + WASM virtual machine do not need to incur, or only incur in smaller overhead scenarios.

Below are some basic performance test cases, specifically selecting interfaces from the C++ side that do not perform much computation to compare the performance differences of cross-language invocation and parameter passing.

  • Rust Test Code

let mut root = Node::new();
let node = Node::new();

let start = App::get_elapsed_time();
for _ in 0..10000 {
	root.set_transform_target(&node);
}
p!("object passing time: {} ms", (App::get_elapsed_time() - start) * 1000.0);

let start = App::get_elapsed_time();
for _ in 0..10000 {
	root.set_x(0.0);
}
p!("number passing time: {} ms", (App::get_elapsed_time() - start) * 1000.0);

let start = App::get_elapsed_time();
for _ in 0..10000 {
	root.set_tag("Tag name");
}
p!("string passing time: {} ms", (App::get_elapsed_time() - start) * 1000.0);

  • Lua Test Code

local root = Node()
local node = Node()

local start = App.elapsedTime
for i = 1, 10000 do
	root.transformTarget = node
end
print("object passing time: " .. tostring((App.elapsedTime - start) * 1000) .. " ms")

start = App.elapsedTime
for i = 1, 10000 do
	root.x = 0
end
print("number passing time: " .. tostring((App.elapsedTime - start) * 1000) .. " ms")

start = App.elapsedTime
for i = 1, 10000 do
	root.tag = "Tag name"
end
print("string passing time: " .. tostring((App.elapsedTime - start) * 1000) .. " ms")

Running Results

Rust + WASM:
object passing time: 0.6279945373535156 ms
number passing time: 0.5879402160644531 ms
string passing time: 3.543853759765625 ms

Lua:
object passing time: 6.7338943481445 ms
number passing time: 2.687931060791 ms
string passing time: 4.2259693145752 ms

It can be seen that, except for the string type interface parameter passing, the performance of other types of interfaces implemented in Dora SSR for Lua cross-language invocation is almost an order of magnitude slower than WASM cross-language invocation.

The inference for the string type interface is that the major performance consumption mainly comes from the copying of string objects, and the overhead of cross-language invocation is much smaller than the overhead of memory copying, so the result difference is not significant.

5. User Experience Insights

Introducing the Rust language into game development, I personally experienced a productivity boost that differs from traditional methods, especially in assisting code generation with large language models (like ChatGPT). Compared to traditional C or C++, Rust’s strict compiler provides a more robust and secure programming environment for game development.

For instance, when using large language models to assist coding, while the generated code in C or C++ can often compile, many hidden errors and defects may still exist at runtime. These issues may include memory leaks, pointer misuse, or reference misuse, which are common and difficult-to-debug problems in game development.

However, in Rust, many such problems can be effectively captured and corrected at the compilation stage, thanks to Rust’s ownership and borrowing mechanisms, as well as its design advantages in type safety and memory safety.

By introducing support for Rust in the Dora SSR game engine, I found that writing game scripts is not only safer but also more efficient. This makes game development no longer a process of debugging errors but rather a more focused process of creating and realizing the imagined game.

These advantages of Rust, coupled with the cross-platform capabilities of WASM, greatly expand our game development capabilities and possibilities.

6. Conclusion

Choosing Dora SSR + Rust as a game development tool is not only a pursuit of cutting-edge technology but also a new exploration of the game development process. Here, I sincerely invite every friend who loves game development to join our community and explore this exciting technological journey together.

Our QQ group is here, welcome to join: 512620381

Author Introduction

Li Jin: Big Data Engineer in the financial industry, author of the open-source software Dora SSR and Yuescript.

Project Introduction

Dora SSR (Dora Wonder Engine) is a game engine for rapidly developing 2D games on various devices. It comes with an easy-to-use development toolchain that supports direct game development on mobile phones and open-source handheld devices.

Experience of Developing Cross-Platform Games with Rust

Project Repository

https://gitee.com/pig/Dora-SSRhttps://github.com/IppClub/Dora-SSR

Popular Articles

The World’s First “Open Source” Renowned Family

– Robin Complained About Closed Source, Zuck Directly Responded…

Rural Chicken is “Open Source” Now

Is the Open Source Secondary Screen “Operating System” Built on Electron a Productivity Tool or a Beautiful Waste?

Legendary Programmer Fabrice Bellard Releases Audio Compression Tool TSAC

Experience of Developing Cross-Platform Games with Rust⬆️ Wuhan Source Creation Conference is Open for Registration

Leave a Comment