WebAssembly in the Cloud

Overview of MUDs

  • Data-Driven — an engine presents a gaming experience by reading from a fixed-schema set of data
  • Code-Driven — a driver (what we nowadays would call a virtual machine) loads user/wizard-supplied code files on demand to provide the gaming experience

I personally have no interest in the former. For the latter, the best example of a code-driven MUD is the famous LPMud. An LPMud has 3 core components:

  • The game driver (virtual machine) — This is a stateful process that runs on a host, providing a network interface and exposes a suite of “efuns” (external functions) to LPC code.
  • The mudlib — Code written in LPC that provides the foundation of the game, e.g. weapons, objects, economy, guilds, magic, rooms, etc.
  • User (wizard) Code — Code written in LPC by players of the game. This code utilizes the mudlib to create the virtual world in which players inhabit.

LPC is a blueprint or prototype based language. Every file (e.g. lib/weapons/sword.c) is a game object. If a game object can be cloned, then multiple instances of the blueprint can exist in memory. In the case of a sword, the blueprint object is lib/weapons/sword and a specific instance of a sword (the one my character is wielding that is worn and about to break, for example) might be lib/weapons/sword#127.

Before I get into how any of this relates to WebAssembly, let’s take a look at a sample bit of LPC code:

inherit “/lib/room”;void create() {
::create();
set_short(“a simple room”);
set_long(“A simple room in a simple building.”);
set_description(“This is a simple room in a simple building. It is very nice.”);
add_exit(“north”, “/realms/descartes/north_room”);
}

This code might exist in a file like areas/kevin/simple_room.c. It should be fairly obvious the benefits of having user-created code like this rather than admin-defined fixed data. This lets the game evolve over time and get more and more features that can be supplied by users rather than a single core developer.

waSCC Overview

As I was developing waSCC, one thought kept gnawing at the back of my brain: can I use waSCC to let people write MUD code as wasm actors?

I think the answer is yes.

waSCC as an Evolution of LPMud

Recall the 3 components of an LPMud:

  • Game Driver (VM)
  • Mudlib (LPC)
  • User Code (LPC)

waSCC is a WebAssembly host, and as such is a virtual machine that executes wasm modules. This means we can probably use waSCC runtime hosts as components of a distributed game driver.

A mudlib is a core set of functionality that is securely exposed to user code that provides foundational building blocks for creating virtual worlds and game experiences. A waSCC capability provider seems like an ideal way to provide a mudlib. User code could make requests of the mudlib to do everything from set state (remember this is a distributed system, state isn’t local to a process) to communicate with players, trigger weather events, initiate combat, and much more.

Finally, user code in an LPMud is written in LPC. User code in a waSCC-based MUD would be written in any language that compiles to a waSCC-compliant actor .wasm file.

As a thought experiment, let’s take a look at what it might look like to re-write the previous LPC example as a Rust-based waSCC actor (for information on creating waSCC actors, check out this tutorial:

#[macro_use]
extern crate wasmud_mudlib as mudlib;
gameobject_handlers! { mudlib::msgs::CREATE => create }fn create(msg: mudlib::msgs::CreateMessage) -> HandlerResult<()> {
mudlib::room::create(); // Perform default room setup
set_short(“a simple room”);
set_long(“A simple room in a simple building.”);
set_description(“This is a simple room in a simple building. It is very nice.”);
add_exit(“north”, “/realms/descartes/north_room”);
}

It looks pretty similar to the original LPC code, but what you don’t see here is that because this is a WebAssembly module running under a waSCC host, it’s automatically getting enterprise-grade scalability and security and a pile of other features.

We can continue down this road and see what it might look like for the room to clone an NPC and move it into the room upon creation:

fn create(msg: mudlib::msgs::CreateMessage) -> HandlerResult<()> {
mudlib::room::create(); // Perform default room setup
set_short(“a simple room”);
set_long(“A simple room in a simple building.”);
set_description(“This is a simple room in a simple building. It is very nice.”);
add_exit(“north”, “/realms/descartes/north_room”);
let dragon = clone_object(“/npcs/dragon”);
dragon.move(this_object())?;
}

The cool new stuff here is clone_object and the move function on the dragon variable. In traditional LPC, since it was running in a single monolith, code like this would directly instantiate the dragon.c file by running it through the VM interpreter and then immediately add the dragon reference (e.g. /npcs/dragon#1284) to the room’s _inventory field.

This code, by virtue of running under waSCC, is far more powerful. The clone_object function will ask the mudlib provider to instantiate another actor (/npcs/dragon.wasm). Our user code does not care where or how this dragon is instantiated. Next, calling move on the dragon will ask the (distributed) mudlib to move the dragon into this room, triggering all appropriate real-time events and dealing with all the ugly things in the background like replication, eventual consistency, etc.

Summary

I would love to hear your thoughts on this as a potential project. Do you think it’s a good idea? Should I continue a series of blog posts as I attempt to write some of this MUD in waSCC? Let me know!

To stay up to date, you can keep in touch with me on Twitter and you can check out the waSCC organization on GitHub.

In relentless pursuit of elegant simplicity. Tinkerer, writer of tech, fantasy, and sci-fi. Converting napkin drawings into code for @CapitalOne