WebAssembly in the Cloud - Will it MUD? ⚔️
Kevin Hoffman
Posted on May 22, 2020
Overview of MUDs
Throughout my career, one of the first things that I've done when encountering a new technology is ask myself whether this technology can be used to create a MUD, a multi-user dungeon (or dimension). MUDs, at least historically, are multi-user, online, text-based adventure games. It's quite possible that there are some MUDs that are older than some of the people reading this post. Just because a technology is old doesn't mean it's bad. I'm going to over-simplify a bit, but MUDs tend to come in two flavors:
- 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
waSCC, WebAssembly Secure Capabilities Connector, is a WebAssembly host runtime that dynamically binds cloud native capabilities to actors (WebAssembly modules). The goal of waSCC is to let developers define pure business logic and deploy it in a small, secure WebAssembly module that is completely decoupled from the capabilities that satisfy non-functional requirements.
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
Back in the good old days, an LPMud was a single process running on a machine to which players would telnet
in order to play. If we're going to build a modern MUD, then it needs to be a distributed, cloud-native system that can dynamically scale to handle changes in load. Thankfully, this is precisely what waSCC is designed to do.
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
In conclusion, it is my hypothesis that we can use waSCC, actors, and secure capability providers to create a MUD that lets players write code that looks and feels as simple as the original LPC, but automatically deals with running in a distributed, dynamically scaling, multi-user environment in the cloud.
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.
Posted on May 22, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.