<![CDATA[Meseta on Game Dev]]>https://meseta.dev/https://meseta.dev/favicon.pngMeseta on Game Devhttps://meseta.dev/Ghost 6.22Sat, 14 Mar 2026 17:19:20 GMT60<![CDATA[ServerKit GameMaker: Easily Manage GameMaker Servers]]>https://meseta.dev/serverkit-gamemaker/658cdb9ebf71b60001190dccThu, 28 Dec 2023 03:26:23 GMT

One of the more annoying aspects of running GameMaker servers is managing and updating them. Unless you're a seasoned backend developer who's familiar with all the tools like docker, kubernetes, CI/CD pipelines, bash scripting, and so on, doing server administration is a chore and there's a lot to learn.

To help with this, I've cooked up a server-management tool that can be easily installed, to create a nice web interface for managing your gamemaker servers. The idea is that once you add this to a server, you can do all your management from a web interface. Saving you from having to deal with scripts, remote access, and stuff like that. I call it ServerKit GameMaker, and it's open source and built in GameMaker itself, in case you want to get your hands dirty and dive into it.

How to manage GameMaker Servers with SKGM

Before I dive into the installation and setup, let me walk you through what having SKGM looks like: Once you have SKGM installed on the server, the first time you go to the management console, you'll be asked to set up an admin password.

After you set up your password, and log in with it. You'll see the Dashboard. This is where your running gamemaker server will show up. For now, it's empty. Click on Manage Deployments to get to your deployment list:

In the Deploy a new version section, you can select your gamemaker server that you want to deploy, in *.AppImage format. This is what you get if you do an Ubuntu Build from the IDE.

Once you've uploaded it, you can go back to Dashboard, where you can now see your current server running

ServerKit GameMaker: Easily Manage GameMaker Servers

From here, you can see the streaming logs from your server, as well as restart your server.

In the future, if you have a new version of the server, you can upload a new version in the Manage Deployments section. And if you need to roll back to a previous version, you can do it from there too, since the old version will show up in the Version history section

ServerKit GameMaker: Easily Manage GameMaker Servers

That's all there is to it. You can easily manage GameMaker servers this way.

Installing ServerKit GameMaker

There are several ways to install ServerKit GameMaker. Ultimately, it is itself a GameMaker game built for Ubuntu, and so running it is done in the same way as running any GameMaker program on Ubuntu.

However, for the purpose of this blog post, I'll walk through the easiest way to get started from zero, and is similar to what I outlined in this post, but rather than set up a server that can be used for both building and running, we'll set up a server that just does the running part (so it may be useful to have both, one for building and testing, one for hosting)

GameMaker Servers in the Cloud
Here’s the easiest way I’ve found to run GameMaker servers in the cloud, without needing to learn a new language or install new software
ServerKit GameMaker: Easily Manage GameMaker Servers

As with the previous post, this method will be using Digital Ocean as the VPS, and at the time of writing, this will run you a cost of about $4/mo - $6/mo.

Setting up the server

Exactly like the previous post, after you register for an account, go to Droplets and click Create Droplet.

ServerKit GameMaker: Easily Manage GameMaker Servers

In the next screen, in the Choose Region, select whichever region makes most sense for you. Servers in different regions affect the ping between the server and users. If servers are far away, then the ping is higher. Also note: some regions don't have the cheapest $4/mo tier available. So click around.

ServerKit GameMaker: Easily Manage GameMaker Servers

In the next Choose an image section, pick Ubuntu, and, unlike the previous post, this time let's pick 22.04 (LTS) x64. The reason for this is it's a slightly newer version, and this time I made sure my setup script supports this version.

ServerKit GameMaker: Easily Manage GameMaker Servers

As with in the other post, in the Choose Size section, select the size of machine you want. The cheapest $4/mo option in the Regular section more or less works, but there's some risk that you will run out of RAM, and it'll cause things to just crash. $6/mo is safer. NOTE: if you don't see the $4/mo option, click on the left arrow button in the list, or select a different region.

ServerKit GameMaker: Easily Manage GameMaker Servers

In the next Choose Authentication Method, pick either option. I suggest you create an SSH Key as it's more secure, but also the Password option is fine. In the previous post, I suggested using Password as that is what GameMaker IDE supports, but in this case, either option is fine. Using SKGM, you usually don't have to ever log into the machine, and the full setup can be done without ever doing that.

ServerKit GameMaker: Easily Manage GameMaker Servers

In the next section, expand the Advanced Options section, and select Enable IPv6, and Add Initialization scripts. This last point is the biggest difference between the previous post and this one.

ServerKit GameMaker: Easily Manage GameMaker Servers

With the Add Initialization scripts selected, you will see a big textbox, where you can paste the below script.

Note: where it downloads SKGM in the script below, the line which reads curl -L https://github.com/meseta/skgm/releases/download/v1.1.1/skgm.AppImage -o /usr/local/bin/skgm.AppImage. This downloads v1.1.1, but you should check if there is a newer version of it, and edit the version number in the script accordingly to get the latest version.

#!/bin/bash

apt-get update
apt-get install --no-install-recommends --yes \
  curl \
  ca-certificates \
  gpg \
  gpg-agent \
  dirmngr

# Check if deb is in sources.list
echo "deb http://security.ubuntu.com/ubuntu xenial-security main" > /etc/apt/sources.list.d/xenial-security.list
echo "deb http://security.ubuntu.com/ubuntu focal-security main" > /etc/apt/sources.list.d/focal-security.list
gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 40976EAF437D05B5 3B4FE6ACC0B21F32
gpg --export 40976EAF437D05B5 3B4FE6ACC0B21F32 >/etc/apt/trusted.gpg.d/security.ubuntu.com.gpg

apt-get update
apt-get install --no-install-recommends --yes \
  libxxf86vm1 \
  libgl1 \
  libssl1.1 \
  libxrandr2 \
  libglu1-mesa \
  libcurl4 \
  libopenal1 \
  xvfb \
  libssl1.0.0 \
  libcurl3-gnutls \
  lsb-release \
  nginx 

# downlaod SKGM
mkdir -p /usr/local/bin
curl -L https://github.com/meseta/skgm/releases/download/v1.1.1/skgm.AppImage -o /usr/local/bin/skgm.AppImage
chmod +x /usr/local/bin/skgm.AppImage

# add unpriviledged user
adduser --disabled-password --gecos "" skgm
usermod -L skgm

# install the fake display startup scripts
cat >/etc/systemd/system/gamemaker-fake-display.service <<EOF
[Unit]
Description=GameMaker Fake Display
 
[Service]
Restart=on-failure
ExecStart=Xvfb :0 -screen 0 400x400x24
 
[Install]
WantedBy=default.target
EOF

# SKGM
cat >/etc/systemd/system/skgm.service <<EOF
[Unit]
Description=SeverKit GameMaker
Requires=gamemaker-fake-display

StartLimitBurst=5
StartLimitIntervalSec=30

[Service]
Restart=on-failure
Environment="DISPLAY=:0"
Environment="SKGM_PORT=5001"
ExecStart=/usr/local/bin/skgm.AppImage --appimage-extract-and-run
User=skgm
Group=skgm
 
[Install]
WantedBy=default.target
EOF

chmod 664 /etc/systemd/system/gamemaker-fake-display.service
chmod 664 /etc/systemd/system/skgm.service

systemctl daemon-reload
systemctl enable gamemaker-fake-display
systemctl enable skgm
systemctl start gamemaker-fake-display
systemctl start skgm

# self-signed certificate and nginx reverse proxy
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout /etc/ssl/private/nginx-selfsigned.key -out /etc/ssl/certs/nginx-selfsigned.crt  -subj "/C=US/ST=New York/L=New York City/O=Internet/OU=./CN=./emailAddress=."

cat >/etc/nginx/sites-enabled/gamemaker-5000.conf <<'EOF'
server {
  listen 443 ssl;
  ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
  ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
  location / {
    proxy_set_header Host $host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_http_version 1.1;
    proxy_pass http://127.0.0.1:5000;
  }
}
server {
  listen 8443 ssl;
  ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
  ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
  client_max_body_size 100M;
  location / {
    proxy_set_header Host $host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_http_version 1.1;
    proxy_pass http://127.0.0.1:5001;
  }
}
EOF

rm /etc/nginx/sites-enabled/default
systemctl restart nginx

Here's the same script in a Gist:

Initialization script for a quick SKGM-controlled server on DigitalOcean, Ubuntu 22.04
Initialization script for a quick SKGM-controlled server on DigitalOcean, Ubuntu 22.04 - skgm-cloud-init.sh
ServerKit GameMaker: Easily Manage GameMaker Servers

This script installs all the dependencies for running GameMaker games; it downloads and installs SKGM as a system service; and it also installs nginx to act as a reverse proxy. This uses a self-signed certificate for now, but you can put Cloudflare in front of it.

With all these filled, click Create Droplet, and wait about 10 minutes for it to spin up.

Firewall Setup

As with before, a firewall is good to have, and you can set this up while you wait for the droplet to spin up. Click on the Networking section in the sidebar, then Firewalls tab, and then Create Firewall

ServerKit GameMaker: Easily Manage GameMaker Servers

This time, the firewall is a bit different as we need to add port 8443 for the SKGM admin console. For extra security, you could change the Sources for that port to your own IP address, which will prevent anyone else outside from accessing the admin console. This is desirable, since if someone breaks in, they would potentially be able to run whatever they want on your server.

In the below screenshot, I've selected to add HTTPS on port 443 since in my case, I will be running a website on that port. You may want to enable the ports that you use for your game. And I've added the needed port 8443 for SKGM, 123.45.67.89 is an example IP address for the purpose of this screenshot, you'd put your own IP in there.

ServerKit GameMaker: Easily Manage GameMaker Servers

At the bottom of the page, in the Apply to Droplets section, type the name of the droplet you just created, to apply the firewall to it

ServerKit GameMaker: Easily Manage GameMaker Servers

At this point, you can set up a reserved IP address for your droplet as well. I cover that in my other post.

Setting up SKGM

Once your droplet is up and running, the first thing you must do is open up SKGM's web UI and set an admin password. If you don't do this, someone else visiting your server will be able to do it.

To go to the web UI, get the IP address of your Droplet.

ServerKit GameMaker: Easily Manage GameMaker Servers

And put it into the URL of your browser, adding port 8443, which is the port where SKGM's web UI is hosted: https://xx.yy.zz.ww:8443. You will see this, but don't panic!

ServerKit GameMaker: Easily Manage GameMaker Servers

The reason you're seeing this is because the setup uses a self-signed certificate, which the browser does not trust. To get rid of this, you would need to set up a proper domain name, and provisioning a proper certificate (or by using CloudFlare in front of your installation). But for now, we have to live with using a self-signed certificate, and seeing this sometimes.

Click Advanced, and then Proceed to xx.yy.zz.ww (unsafe)

ServerKit GameMaker: Easily Manage GameMaker Servers

Once you do this, you will see the password setup screen, and you can follow the steps outlined at the start of this post!

ServerKit GameMaker: Easily Manage GameMaker Servers

Congratulations, you've set up a SKGM managed server, and can now easily upload and deploy new versions of your gamemaker server straight from a web browser. I'll be covering how to set up a domain name so that you don't have to see the "Your connection is not private" message all the time in a future post.

]]>
<![CDATA[HyperText GameMaker: Make Websites in GameMaker]]>https://meseta.dev/server-make-websites/6585f8484396de000145c156Sat, 23 Dec 2023 01:27:46 GMT

If you cut through the crap on the internet about what is and isn't web development, and whether HTML is or isn't a programming language, the core of it is that a website is pretty much something your browser can talk to a server for and receive HTML, with varying amounts of JavaScript to spice things up. For more complex dynamic websites (the ones that can change their content rather than just being static pages, like blogs, games, and web apps), the server had the option of deciding exactly what HTML to give to you, and/or the JavaScript that is loaded into the browser may be doing that. Either way, eventually the HTML gets changed in order to show you something different.

Back in the early 2000s, our main way of doing this was called server-side scripting. Back then, PHP had just exploded on the scene, and was rapidly dominating the internet, a position it keeps even today. Back then, the way PHP worked was it was simply a program that the web server (usually Apache) called in order to handle a request from the browser. Your PHP code would run, and it would literally print out the HTML. You could change your page by changing what PHP printed, literally with if statements to change what was printed. Apache would take this printout and send it back to the user's browser.

Somewhere in the mid 2010s, JavaScript SPAs got popular (stuff like React). These worked slightly differently in that they didn't rely on the server printing HTML. Instead, a simple page would be loaded, and once the page loaded, JavaScript in the browser would run, and dynamically change what the user was seeing in real-time. Sometimes fetching bits of JSON from a server, to decide what to display.

These days, the pendulum is swinging the other way again, with things like JavaScript starting to use more SSR (server-side rendering), where the server is once again being asked to print out some HTML to send to the browser, where it's incorporated in to the page.

What does this history have to do with making websites in GameMaker? Well, what's stopping us from outputting HTLM from GameMaker, just as PHP did all those years ago? What's stopping us from incorporating SSR-like abilities of rendering little bits of HTML at a time to send to the browser? Nothing. Nothing at all. GameMaker can be made to do this, and can become a webserver too. I decided to explore that route recently to come up with what I call HyperText GameMaker, a GML framework that enables you to make webservers and website directly using GML.

This article is aimed at intermediate GameMaker devs, and ideally developers who already have some experience in (or willingness to learn) HTML, since we can't escape the need to write HTML, who want to dabble in making websites, either to be used from the web OR as a component of their game. Follow along to make your first website in GameMaker.

Download and install the Library

First step is to grab the library, the official website is here (where you may find some additional usage guides):

HyperText GameMaker
A web-server framework for GameMaker written in pure GML.
HyperText GameMaker: Make Websites in GameMaker

The downloads are here on the GitHub

Releases · meseta/htgm
A web-server framework for GameMaker written in pure GML. - meseta/htgm
HyperText GameMaker: Make Websites in GameMaker

Download the dev.meseta.htgm.yymps package, this is a GameMaker local package, and can be imported into a new project from the Tools > Import Local Package menu.

The library comes with a lot of ... stuff, but don't worry about it too much, it needs most of those things to work correctly. You may occasionally get a conflict with well-known libraries like SnowState which the library uses internally, or my other Logger and Sentry libraries (hopefully the new package management stuff GameMaker are working on will solve this).

Create your first website

Find an object that you will use to hold the setup and start of your website. You can also forgo an object entirely and use a global, it actually doesn't matter too much, it's up to you, as long as server.start() runs the game enters the first room.

HyperText GameMaker: Make Websites in GameMaker
server = new HttpServer(5000);
		
server.add_path("", function(_context) {
	_context.response.send_html("<h1>Hello World</h1>")
});

server.start();

url_open("http://localhost:5000");

Why does server.start() have to run after entering the first room?

This is due to how GameMaker's startup process works. As you may know, putting any code in a script file and outside the scope of any function() allows you define globals and stuff. However, all this stuff happens before we enter the first room, which means you can't spawn any objects. It'll just fail.

Because objects with async-networking events are the only way to make TCP servers currently, the HTGM library has to spawn an object when you run server.start() that has an async-networking event in it. It can't do that until the game has entered the first room.

Personally, I actually create the server as a global variable in one of the scripts, and put server.start() in a call_later to run a frame later. Saves having to use objects for it.

Once you hit run, your browser will pop up, and hopefully you'll see the page:

HyperText GameMaker: Make Websites in GameMaker

Congratulations, you've just made your first "website". At this point, if for some reason you wanted to host this on a server, I have a little guide for it here:

GameMaker Servers in the Cloud
Here’s the easiest way I’ve found to run GameMaker servers in the cloud, without needing to learn a new language or install new software
HyperText GameMaker: Make Websites in GameMaker

Using a Render constructor

For a more complex website, we're going to want to use HTGM's support for using constructors to render pages instead of the bare-bones basic add_path() method (you can read more about these things in the usage guide).

So let's create a new constructor that will output a proper HTML page instead of just <h1>Hello World</h1>

Create a new script called ViewIndex, with the following:

function ViewIndex(): HttpServerRenderBase() constructor {
	// View setup
	static path = "";
	
	// Static properties
	static title = "My GameMaker Website";
	static content1 = "<h1>Hello World</h1>";
	static content2 = "<p>This is a website made in gamemaker</p>";
	
	static render = function(_context) {
		return @'
			<!DOCTYPE html>
			<html lang="en">
			<head>
				<meta charset="utf-8">	
				<meta name="viewport" content="width=device-width, initial-scale=1">
				<title>'+ self.title + @'</title>
			</head>
			<body>
				'+ self.content1 + @'
				'+ self.content2 + @'
			</body>
			</html>
		';
	}
}

The big string above is what a more fleshed out HTML page looks like. Plus, we're having GML insert various things using variables.

Then update your server code to the following (we're replacing the add_path() with add_render() to tell the server we want to use ViewIndex in the server.

server = new HttpServer(5000);
		
server.add_render(ViewIndex);
server.start();

url_open("http://localhost:5000");

If you run the game again, you'll see mostly the same thing as before, but now we're set up to extend this a lot more.

HyperText GameMaker: Make Websites in GameMaker

Adding CSS

A blank page doesn't look very good, we need to add some style. In steps CSS. I won't go into the specifics of how CSS and HTML work together, but here's the example of how you can use it.

In this example, instead of writing extensive CSS myself, I'm going to pick the nice and lightweight CSS library Pico.CSS, which gives us a bunch of pre-written stuff. It's actually what I'm using for the main HTGM website itself, so you'll be able to see some similarity.

If you go to the Pico.CSS's docs page and see the "Usage" section, it'll explain how to install it:

Documentation
Pico works without package manager or dependencies! There are 4 ways to get started with Pico CSS: manually, from a CDN, with NPM, or with Composer.
HyperText GameMaker: Make Websites in GameMaker
HyperText GameMaker: Make Websites in GameMaker

The above is a screenshot from their documentation page for how to install it. We're going to use this "Install from CDN" part, since loading it from CDN is the fastest way to do it.

So, we edit ViewIndex() and change it to the following, we're just adding that <link> tag, plus putting the contents inside a <main> tag.

function ViewIndex(): HttpServerRenderBase() constructor {
	// View setup
	static path = "";
	
	// Static properties
	static title = "My GameMaker Website";
	static content1 = "<h1>Hello World</h1>";
	static content2 = "<p>This is a website made in gamemaker</p>";
	
	static render = function(_context) {
		return @'
			<!DOCTYPE html>
			<html lang="en">
			<head>
				<meta charset="utf-8">	
				<meta name="viewport" content="width=device-width, initial-scale=1">
				<title>'+ self.title + @'</title>
				
				<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
			</head>
			<body>
				<main class="container">
					'+ self.content1 + @'
					'+ self.content2 + @'
				</main>
			</body>
			</html>
		';
	}
}

Now, when we run the project, we get a much nicer-looking website. Even if it's just a title and some text, the CSS library has given us a colors-cheme, font selection, and a bit of extra control over the layout. So even a basic website as this starts looking more professional.

HyperText GameMaker: Make Websites in GameMaker

Adding nested pages

While we could just duplicate this page multiple times, that makes the website much harder to maintain, because if we wanted to change the design a little, we'd have to update it on every page. Instead, we can use ViewIndex as a template page, and have it load the other pages into it (this method is described in the usage guide).

So, let's create a couple of content pages:

function ViewMain(): HttpServerRenderBase() constructor {
	// View setup
	static path = "main";
	static redirect_path = "";
	
	static render = function(_context) {
		return @'
			<h1>Welcome</h1>
			<p>
				Welcome to my website made in GameMaker!
			</p>
		';
	}
}
function ViewAbout(): HttpServerRenderBase() constructor {
	// View setup
	static path = "about";
	static redirect_path = "";
	
	static render = function(_context) {
		return quote_fix(@'
			<h1>About Me</h1>
			<p>
				Hi, I`m a gamedev who is making a website using GameMaker
			</p>
		');
	}
}

As you can see, both of these views are very simple, and contain just the HTML for the content they're responsible for. Also note the inclusion of redirect_path. This tells HTGM that any request for this site should internally redirect to the root path, which is an empty string (the path provided by ViewIndex) since we want ViewIndex to render the template page first.

Then, in ViewIndex, we want to update it to use the render functions from the constructors that we redirected from, to incorporate their render into the index template:

function ViewIndex(): HttpServerRenderBase() constructor {
	// View setup
	static path = "";
	
	// Static properties
	static title = "My GameMaker Website";
	
	static render = function(_context) {
		var _render = _context.pop_render_stack();
		var _content = is_method(_render) ? _render(_context) : ViewMain.render(_context);
		
		return @'
			<!DOCTYPE html>
			<html lang="en">
			<head>
				<meta charset="utf-8">	
				<meta name="viewport" content="width=device-width, initial-scale=1">
				<title>'+ self.title + @'</title>
				
				<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
			</head>
			<body>
				<main class="container">
					'+ _content +@'
				</main>
			</body>
			</html>
		';
	}
}

Finally, don't forget to update your server object, to register these new views:

server = new HttpServer(5000);
		
server.add_render(ViewAbout);
server.add_render(ViewMain);
server.add_render(ViewIndex);
server.start();

url_open("http://localhost:5000");

When you run the project now, you'll be able to see the main page has been updated to make use of the content on ViewMain:

HyperText GameMaker: Make Websites in GameMaker

But also, if you navigate to http://localhost:5000/about, you'll now see the contents of ViewAbout incorporated into ViewIndex

HyperText GameMaker: Make Websites in GameMaker

Adding a Navigation Bar

Now that we have more than one page, we need a navigation bar at the top of the screen to add links.

To stay with the theme of re-usable components (more info on that in the usage guide), we're going to create a navigation bar component, as well as individual components for each nav link. The reason for this is to make it really easy to edit the links - you can edit just the one component rather than have to edit lots of individually copy/pasted ones.

function ComponentNavigation(): HtmlComponent() constructor {
	static render = function(_context) {
		static _links = [
			new ComponentNavigationLink(ViewMain.path, "Home"),
			new ComponentNavigationLink(ViewAbout.path, "About"),
		];
	
		return @'
			<nav class="container-fluid">
				<ul>
					<li><strong>My Website</strong></li>
				</ul>
				<ul>
					'+ HtmlComponent.render_array(_links, "", _context) + @'
				</ul>
			</nav>
		';
	};
}

function ComponentNavigationLink(_path, _text): HtmlComponent() constructor {
	self.path = _path;
	self.text = _text;

	static render = function(_context) {
		return $"<li><a href='{self.path}'>{self.text}</a></li>";
	}
}

As you can see, I've added a static array of ComponentNavigationLink, instantiating each using the path property of their respective views (this is, by the way, why those variables were statics). We could, instead have just written "about" and "main" instead of ViewAbout.path and ViewMain.path, but this is just easier to maintain.

Note: now we're inheriting from HtmlComponent() instead of HttpServerRenderbase() The reason for this is HtmlComponent() just contains some lightweight functions to make your life easier when making components, whereas HttpServerRenderBase() has a bunch of internal methods used by the server to figure out how to render your page.

The navigation component can be used inside the ViewIndex:

function ViewIndex(): HttpServerRenderBase() constructor {
	// View setup
	static path = "";
	
	// Static properties
	static title = "My GameMaker Website";
	
	static render = function(_context) {
		static _navigation = new ComponentNavigation();
		
		var _render = _context.pop_render_stack();
		var _content = is_method(_render) ? _render(_context) : ViewMain.render(_context);
		
		return @'
			<!DOCTYPE html>
			<html lang="en">
			<head>
				<meta charset="utf-8">	
				<meta name="viewport" content="width=device-width, initial-scale=1">
				<title>'+ self.title + @'</title>
				
				<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
			</head>
			<body>
				'+ _navigation.render(_context) + @'
				<main class="container">
					'+ _content +@'
				</main>
			</body>
			</html>
		';
	}
}

Now, running the website, you'll be able to see the new navigation bar, with links you can click to get to the other pages!

HyperText GameMaker: Make Websites in GameMaker

This is as far as I'll go in this article. Hopefully you can see how you can create websites with GameMaker, making use of re-usable components. There's so much else that can be done from here, to make more complex websites, even whole web-apps. Take a look at the source code for the HTGM website itself on the git repo if you're looking for more complete examples. And if you want to throw this on the internet, rather than run from your computer all the time, you'll need to put this "game" on a server. I have a post for that here.

]]>
<![CDATA[GameMaker Servers in the Cloud]]>https://meseta.dev/server-gamemaker/6580d443bc645f00012f25d0Tue, 19 Dec 2023 18:24:54 GMT

I'm going to be blogging a lot about running GameMaker on the server. I personally do a lot of builds using a CI/CD system that automatically makes and deploys GameMaker linux builds when making a git push, however this takes a bit of setup, so I want to provide some simpler methods.

In this article, I'll share with you the quickest way I've figured out to build and host GameMaker servers. It's not the most polished way, it's not the cheapest, and it's not ideal for long-term hosting, however it is the fastest way to get started, requiring the least amount of learning new things.

Note: I will be using Digital Ocean as the VPS solution, which at the time of writing will run you a cost of about $4/mo - $6/mo.

What are we doing?

We're going to set up your own always-on cloud server, which will serve as both the remote builder and the server on which to run a single GameMaker server, and we're going to do it in a way which requires only the use of a browser, and the GameMaker IDE itself. No need to learn any new languages, no need to install new software, no need to leave your own computer running all the time.

GameMaker is capable of running on Linux, this is ideal for running as a server. But if you're running Windows on your desktop, GameMaker IDE needs to connect to a linux machine in order to do the build. So, we will make a server that GameMaker IDE can connect to to run the build. And then we will use the same server to host and run the built app, two birds with one stone!

The "server" I am referring to will be a VPS. Or in other words, one that you "rent" and pay for by the hour as an online service. You can easily turn it on and off though, so it's good to experiment with and there aren't any hassles cancelling it. The service I have chosen is Digital Ocean, which, in my opinion, has one of the easiest-to-use services out there, and the prices are very reasonable and there are few surprises.

Setting up the server

After you register for an account, go to Droplets and click Create Droplet. "Droplets" are what Digital Ocean calls their normal servers. You may see the term "Virtual Machine", "Virtual Private Server", "Cloud Server", or "Cloud Instance" used mostly interchangeably.

GameMaker Servers in the Cloud

In the next screen is the Choose Region section. The region impacts the ping between the server and the users of the server. If the server is far away from the user, then ping is higher, this may matter if you're using the server as a game server. For example if you pick San Francisco, and the user is located in Europe, then you may expect pings in excess of 100ms. But a user in California may experience <10ms pings. Aside from this, some regions don't have the cheapest $4/mo tier.

The datacenter doesn't matter too much at this point.

GameMaker Servers in the Cloud

In the next Choose an image section, pick Ubuntu, and 20.04 (LTS) x64, this is the OS that you'll be using. GameMaker only officially supports Ubuntu, and my setup is only tested on 20.04, it might only need a little bit of tweaking to get working on newer versions though.

GameMaker Servers in the Cloud

In the Choose Size section, select the size of machine you want. The cheapest $4/mo option in the Regular section more or less works, but there's some risk that you will run out of RAM, and it'll cause things to just crash. $6/mo is safer. If you have a bigger project, you may start needing the bigger machines. NOTE: if you don't see the $4/mo option, click on the left arrow button in the list, or select a different region.

GameMaker Servers in the Cloud

In the Choose Authentication Method section, switch to Password and create a password. This is necessary as GameMaker IDE wants to connect via password rather than SSH Key.

GameMaker Servers in the Cloud

In the next section, expand the Advanced Options section, and select Enable IPv6, and Add Initialization scripts. This last point is very important, because this is where we will add code to set the server up properly to act as a GameMaker builder and runner. Without this, all we have is a bare-bones basic Linux server.

GameMaker Servers in the Cloud

With the Add Initialization scripts selected, you will see a big textbox, where you can paste the below script

#!/bin/bash

# Check if deb is in sources.list
DEB="deb http://security.ubuntu.com/ubuntu xenial-security main"
grep "$DEB" /etc/apt/sources.list > /dev/null 2>&1
if [ $? -ne 0 ]; then
  echo "$DEB" >> /etc/apt/sources.list
fi  

apt-get update && apt-get install --no-install-recommends --yes \
	curl \
	gnupg \
	ca-certificates \
	build-essential \
	clang \
	libssl-dev \
	libxrandr-dev \
	libxxf86vm-dev \
	libopenal-dev \
	libgl1-mesa-dev \
	libglu1-mesa-dev \
	zlib1g-dev \
	libcurl4-openssl-dev \
	mono-complete \
	zip \
	unzip \
	ffmpeg \
	rsync \
	git \
	libxxf86vm1 \
	libgl1 \
	libssl1.1 \
	libxrandr2 \
	libglu1-mesa \
	libcurl4 \
	libopenal1 \
	xvfb \
	libssl1.0.0 \
	libcurl3-gnutls \
	lsb-release \
	nginx

STEAMRUNTIME=/opt/steam-runtime/
if ! [ -d "$STEAMRUNTIME" ]; then
	mkdir $STEAMRUNTIME
	curl -Ls https://repo.steampowered.com/steamrt-images-scout/snapshots/latest-steam-client-general-availability/com.valvesoftware.SteamRuntime.Sdk-amd64,i386-scout-sysroot.tar.gz | tar -xzf - -C $STEAMRUNTIME
fi

LINUXDEPLOY=/usr/local/bin/linuxdeploy
if ! [ -f "$LINUXDEPLOY" ]; then
	curl -Ls https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage -o linuxdeploy-x86_64.AppImage
	install -m 0755 linuxdeploy-x86_64.AppImage $LINUXDEPLOY
	rm linuxdeploy-x86_64.AppImage
fi

APPIMAGETOOL=/usr/local/bin/appimagetool
if ! [ -f "$APPIMAGETOOL" ]; then
	curl -Ls https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -o appimagetool-x86_64.AppImage
	install -m 0755 appimagetool-x86_64.AppImage $APPIMAGETOOL
	rm appimagetool-x86_64.AppImage
fi

# install the fake display startup script
cat >/etc/systemd/system/gamemaker-fake-display.service <<EOF
[Unit]
Description=GameMaker Server Stuff
 
[Service]
Environment="DISPLAY=:0"
ExecStart=Xvfb :0 -screen 0 400x400x24
 
[Install]
WantedBy=default.target
EOF

# process watcher to ensure no two AppRun are running
# this is needed as of IDE v2023.11.0.121 which won't shut down old AppRuns
cat >/usr/local/bin/gamemaker-process-watcher.sh <<'EOF'
#!/bin/bash
while sleep 1; do if [[ $(pgrep -f "AppRun -debugoutput" | wc -l) -gt 1 ]]; then pkill -f "AppRun -debugoutput"; fi; done
EOF

cat >/etc/systemd/system/gamemaker-process-watcher.service <<EOF
[Unit]
Description=GameMaker Server Stuff
 
[Service]
ExecStart=/usr/local/bin/gamemaker-process-watcher.sh
 
[Install]
WantedBy=default.target
EOF

chmod 744 /usr/local/bin/gamemaker-process-watcher.sh
chmod 664 /etc/systemd/system/gamemaker-process-watcher.service
chmod 664 /etc/systemd/system/gamemaker-fake-display.service

systemctl daemon-reload
systemctl enable gamemaker-process-watcher
systemctl enable gamemaker-fake-display
systemctl start gamemaker-process-watcher
systemctl start gamemaker-fake-display

# self-signed certificate and nginx reverse proxy
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout /etc/ssl/private/nginx-selfsigned.key -out /etc/ssl/certs/nginx-selfsigned.crt  -subj "/C=US/ST=New York/L=New York City/O=Internet/OU=./CN=./emailAddress=."

cat >/etc/nginx/sites-enabled/gamemaker-5000.conf <<'EOF'
server {
  listen 443 ssl;

  ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
  ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;

  location / {
    proxy_set_header Host $host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_http_version 1.1;
    proxy_pass http://127.0.0.1:5000;
  }
}
EOF

rm /etc/nginx/sites-enabled/default
systemctl restart nginx

Here's the same script in a Gist:

Initialization script for a quick GameMaker linux builder on DigitalOcean, Ubuntu 20.04
Initialization script for a quick GameMaker linux builder on DigitalOcean, Ubuntu 20.04 - gamemaker-builder-cloud-init.sh
GameMaker Servers in the Cloud

This script installs all the required tools. Most of this stuff follows the official guidelines from the help center. Except for the last few lines which are needed to get around a weird quirk/bug which is actually useful for us, which I'll talk about in a sec; and also add a self-signed TLS-terminating proxy which we'll use later to connect Cloudflare to, and also serve WSS from.

Once these are all filled, click Create Droplet, and wait about 10 minutes for it to spin up, and install all the needed stuff. You'll be able to tell when this installation is complete when the CPU usage falls.

Firewall Setup

While you wait for the droplet to spin up, you might as well use this time to set up the firewall, which will help secure things. This is good practice to do. Click on the Networking section in the sidebar, then Firewalls tab, and then Create Firewall

GameMaker Servers in the Cloud

In the next dialogue, the only thing to add is some inbound ports to the Inbound Rules (you can leave the Outbound Rules as-is). You will want to keep the SSH (port 22) rule, since that's what GameMaker IDE needs to be able to access the server to make a build. At this point, if you already know what ports you will need for your game, add them, if not, leave it with just the SSH rule.

In the below screenshot, I have selected to add HTTPS on port 443 since I am going to be running a webserver, and a custom TCP 5000 rule since this is a port I frequently use for testing.

GameMaker Servers in the Cloud

At the bottom of the page, in the Apply to Droplets section, type the name of the droplet you just created, to apply the firewall to it

GameMaker Servers in the Cloud

Finally, you will need your public IP address for your newly created droplet. You can find this in your Droplets details page as well as the Droplets list under either IP Address

GameMaker Servers in the Cloud

Setting up GameMaker

Over in GameMaker IDE, we need to set up the IDE to be able to target this server when making builds.

In the target selector (top right of screen) in your project, select the Ubuntu platform (at this point, if you don't have that installed, you'll be asked to do that), and click on the edit icon in the Device list.

GameMaker Servers in the Cloud

In the Device Editor, click Add New Device, and fill in the details.

  • Display Name: this is anything you want
  • Host Name: this is the IP address of your Digital Ocean droplet that you copied earlier
  • User Name: this will be root
  • Password: this is the password you set way back when you were setting up the droplet in the Choose Authentication Method
  • Install Folder: you can leave this as default
GameMaker Servers in the Cloud

And then that's it! Your IDE is now set up to make Ubuntu builds by connecting to the server.

If you need a quick test project to validate that it's working, throw this into a project, and check if it outputs it in the Output console.

show_debug_message($"Am I on linux? {os_type == os_linux ? "yes" : "no" }");

GML considerations

There are a few considerations to make when running GameMaker on the server.

  • Since there is no screen or graphical environment, you will want to turn off the draw event so that you don't waste CPU on drawing something that nobody is going to see This can be done easily by adding the following line of code somewhere in the project near the start: draw_enable_drawevent(false);
  • Don't try to show_message(), it'll crash since there isn't a graphical environment in which to show the message.
  • Since there isn't a need to run at 60 FPS, you may want to pick a lower FPS to reduce CPU usage. Although the exact value you pick depends on the needs of your server.
  • Under the current setup, you won't be able to see any log messages, which may make it hard to see what the server is doing when you're not running it through the IDE

Running the game on the server

Ok, so here's the weird thing. GameMaker IDE v2023.11.0.121 currently has a little bug, where when you hit "Run" with the server targeted, it will build and run the game on the server, and then it'll just leave it running!

I'm quite sure this is unintentional, as every time you hit "Run", a new process will start up, meaning you will end up creating more and more instances of the game, which won't close automatically as you might expect.

To solve this problem, there was a little command at the bottom of the initialization script that is keeping an eye out for any situation where there is more than one copy of the game running (its process name is AppRun), and kill the older process, ensuring that however many times you hit "Run", you'll only have one copy of the game running. (Just be aware: could take up to a second to kill the old process, you may need to add a short delay to the start of your game if you are doing any server work, because the TCP port may not be immediately available before the old process is killed).

The benefit of GameMaker IDE leaving the game running on the server after you hit "Run" is that this is actually the desired behaviour for setting up and leaving your game running on the server! It means whenever you want to update your server, you just select the server in your targets list in the IDE, and hit Run, and then just quit the IDE, the game continues to run in the server in the background.

Unfortunately I suspect they'll fix this behavior at some point, and we'll have to find a new way to leave a server running. A problem for another time.

Cleaning up/Shutting down

When you're done with experimentation, and you want to just nuke everything, you can do so from the Destroy section of the droplet. This will stop any billing associated with the droplet.

GameMaker Servers in the Cloud

Now you know how to run a GameMaker game on a Linux Server in the cloud. It's not the best way of doing it, but it should be the easiest.

]]>
<![CDATA[Translations Management Systems]]>https://meseta.dev/libraries-traduora/657e2320ddf5fa000160def0Sun, 17 Dec 2023 07:59:10 GMT

Want to hear a pet peeve of mine? CSV files.

A concept that takes a while to shake for those learning GameMaker is the idea that a file format should be chosen based on the ergonomics of how you would edit them. Namely: if you use CSVs, you use Excel to edit them and it's tabular data. If you use JSON, you use a text editor to edit them and it's nested structs. And from that single assumption stems a great deal of misconceptions about what file format to choose an when.

But the ergonomics of a data file format like CSV or JSON are only consequential if you use the most basic and generic tools to handle them. You don't have to use Excel or a text editor. In fact, you should get away from that sort of thinking. It's like thinking that there only exists a single tool out there with which to edit pixel art or sound files. Instead, it's worth remembering that data file formats in general are merely a data interchange format. A way for programs to exchange data in a common format without having to rely on custom data formats, and all the complexities that come from that. 99% of the time you shouldn't even need to look at a JSON file or CSV file, in the same way you don't need to open a PNG file in a hex editor to look at the raw bytes, or a DOCX in a text editor to look at the underlying XML that Word docs are made of.

One specific instance of this a question I often see being asked in the GameMaker Discord: "how should I store translations for my game?" A common answer to that is: "load it from a CSV, you can send the file to a translator and they can edit it in Excel". It's not the worst option. At its core, translations somewhat fit tabular data of Excel, and gamemaker can indeed handle CSV files, and Excel is a piece of software that a lot of people have and know how to use. Compared to editing JSON by hand, this seems like a decent choice. But we shouldn't be comparing that to editing JSON files by hand.

What if I were to say: "the file format doesn't actually matter. If the end goal is to have nice ergonomics for a translator to supply translations, why pick Excel at all?" This is what I want to talk about in this article. Specifically, one example of a choice for translation storage that is better than using Excel.

Accompanying this the following codebase:

Releases · meseta/gmtraduora-client
Contribute to meseta/gmtraduora-client development by creating an account on GitHub.
Translations Management Systems

Traduora and translation management systems

My very first "real job" was working at a translation agency, building web tools to help a team of translators keep track of translation jobs. One thing I noted back then was that those professional translators used a type of software called a Translation Management System (TMS) rather than Excel. TMSes have a job of keeping track of all the phrases that need to be translated, and lets multiple translators collaborate on the same project, allowing for them to share the workload easily. It also had features that allowed the original writer to add important context notes about the meaning and usage of phrases, to make sure the tone as set correctly.

Today, there are a handful of free or open source TMSes that are suitable for small indie projects. Most of them are written as cloud software, which is understandable given these days that sort of collaborative work can be done in a browser using a web app. The one I want to highlight and talk about is Traduora:

traduora: translation management platform for teams.
Teams use traduora to reach new users all around the globe. Automate your translation workflow today.
Translations Management Systems

Traduora is a neat open source project that lets you run your own online translation management system. Although it's not as straightforward to run as a desktop app, once you do run it, you have everything you need to invite translators to your project, add passages to be translated, and, importantly, easily export those translations to your game, or in fact, fetch them automatically on load.

I've set up a demo instance of Traduora that anyone can use to test this out: https://traduora-demo.meseta.dev/

Please note: this demo instance should not be used for production purposes. It is for testing and exploration only. I make no guarantees on the reliability and uptime of the instance or the data. Assume the data may be wiped at any moment, or the whole system taken down.

Setting up or hosting your own Traduora instance is a little outside the scope of this post, but if it's something that interests you and you have a gamemaker project that is ready to have this sort of Translation Management, get in touch, I may be able to help out.

Signing up

I've set up this demo Traduora instance just to help people get started. Sign up with any email address.

Translations Management Systems

Creating a project

Traduora lets you create individual projects to keep all your work in. You'd probably want one project per game.

Translations Management Systems

Setting up locales

In Traduora, "Locales" are the languages that your game is in. The first thing you need to do before anything else is set up at least one Locale.

I highly recommend adding English (or whichever is your native language) as a locale alongside the other languages, as Traduora will be able to display that as a "reference language" so that someone doing the translating can see what the term is in English when they make the translation.

Translations Management Systems
Translations Management Systems
Translations Management Systems

Setting up Terms

In Traduora, "Terms" are the individual chunks of language that can be translated. This could range from simply the word "Cancel" as used on a button, to whole paragraphs of dialogue. It's up to you!

Translations Management Systems
Translations Management Systems

At this point you may also want to set up "Labels" to add to your terms. These are small labels to help categorize your terms

Translations Management Systems

Adding your team

If you're working in a team, you can add them. It's possible to set different roles, so different team members are limited in what they can do to the project. Although, please remember: the test instance I set up should not be used for production purposes, only use it for test purposes.

Translations Management Systems

Do the translation

At this point, you have everything set up to be able to start translating. If you head over to the "Translations" section, you can click on one of the languages, and you'll start seeing a list of the Terms you added, and have the opportunity to fill in the translation.

You very likely want to fill in the English translations (or whichever is your native language) first, since these are used as a reference to which the other translations will be made:

Translations Management Systems

Once you have the first reference language done, clicking on another language, you can show those terms alongside the language you're translating:

Translations Management Systems
Translations Management Systems

Exporting the translation

Once you're done with the translation, you can export all the data.

Translations Management Systems

I suggest the JSON option. The resulting file from this export looks like this:

{
    "dialogue": {
        "chapter1": {
            "dave": {
                "0": "OH TO! Je te connais"
            }
        },
        "intro": "Vous êtes arrivé au bureau, vous voyez Dave le comptable devant vous"
    },
    "menu": {
        "button": {
            "cancel": "Annuler",
            "new_game": "Nouveau Jeu"
        }
    }
}

You can easily include this file in your game's additional data, and have gamemaker load in the file using a buffer_load() and a json_parse(). The basic usage is something like:

function read_language(_language) {
  var _buff = buffer_load($"{_language}.json");
  var _json_str = buffer_read(_buff, buffer_text);
  buffer_delete(_buff);
  return json_parse(_json_str);
}
global.translation = read_language("fr");

// somewhere inside the code for the cancel button:
draw_text(_x, _y, global.translation.menu.button.cancel);

Of course, you'd want to add a way to load in one of several languages depending on the player selection.

As you can see, we're using JSON here. We could have exported as CSV too, though it would have required more code that the above to import. But either way, we're not editing the data file by hand, it's merely a data interchange format. The most important thing is that we are using a tool that is eminently more suited to editing translations than Excel is.

But that's not all! The major benefit of a web-based translation tool like Traduora is that you don't have to manually export and handle the translation files. You can do that automatically and have the game fetch the files itself.

Importing/Restoring a Backup

Because my Traduora instance is for testing only, if you do start using it for your project, you should frequently export your terms as a backup. In case the data is wiped, or if you want to migrate your project to a new Traduora instance, you can do it by exporting all your locales, and then re-importing it.

Note: a lot of the properties like the labels and context don't get exported. There is a way to do this through the API though.

Automatically Downloading Translations

To give your game access to your Traduora project, you need to create an API key:

Translations Management Systems
Translations Management Systems

You'll also need your project ID, which is the UUID that's in the URL bar, e.g. https://traduora-demo.meseta.dev/projects/<your project ID>/

Grab my Traduora client library from here:

Releases · meseta/gmtraduora-client
Contribute to meseta/gmtraduora-client development by creating an account on GitHub.
Translations Management Systems

Here's a typical setup:

// Create traduora
traduora = new TraduoraClient("https://traduora-demo.meseta.dev", "<project id>");

// Authenticate and then backup, and then get the English locale
traduora.authenticate("<client id>", "<client secret>")
  .chain_callback(function() {
    return traduora.get_locale("en");	
  })
  .chain_callback(function(_payload) {
    show_debug_message(_payload);
  })
  .on_error(function(_err) {
    show_debug_message("uh oh");
  });

I'm using a combination of libraries here to achieve a convenient interface for making HTTP requests, including what I call Chains, which are Javascript promise-like callback chaining for Gamemaker. This helps us keep a sequence of asynchronous requests running one after the other.

First we have to authenticate the client with the Traduora service, the .authenticate() method takes the client ID and secret created previously, and makes the authentication request. This function returns a Chain, which we can add further callbacks to, which will be executed when the authentication request succeeds.

The first callback we add to the chain is another request to Traduora to fetch the "en" locale. We return this so that it can be added to the chain.

The next callback will run only when the previous fetch to "en" locale succeeds. The _payload passed to it will be the result of the request, which should contain the same JSON format as you would get if you exported it from the web UI. At this point, you would save that _payload somewhere in your translation handling code.

Using the Chain library gives us a lot of options. For example, we could make multiple concurrent requests to load all of the languages at once:

// Authenticate, and then get a bunch of locales in parallel
traduora.authenticate("<client id>", "<client secret>")
	.chain_callback(function() {
		return Chain.concurrent_struct({
			en: traduora.get_locale("en"),
			fr: traduora.get_locale("fr"),
			es: traduora.get_locale("es"),
			de: traduora.get_locale("de"),
		})	
	})
	.chain_callback(function(_results) {
		show_debug_message(_results.en);
		show_debug_message(_results.fr);
		show_debug_message(_results.es);
		show_debug_message(_results.de);
	})

This code will first perform the authentication, and then make multiple requests in parallel, before running the last callback once all requests have completed.

Finally, in the above examples I've chained the get_locale() method calls after authenticate(). This is to ensure that we only call get_locale() after the authentication completes. However, there are often times when enough time passes between authentication that you don't have to do that.

// Authenticate
traduora.authenticate("<client id>", "<client secret>");

// Some time later
traduora.get_locale("en")
	.chain_callback(function(_payload) {
		show_debug_message(_payload);
	});

Hopefully throughout this article you can see that while GameMaker can load CSV or JSON, those file formats should be considered data interchange formats, rather than for human consumption. And just like we use dedicated tools for art and sounds, we should also use dedicated tools for data.

]]>
<![CDATA[Automated Bug Reporting with Sentry for GameMaker games]]>https://meseta.dev/libraries-sentry/65790c3dddf5fa000160dd86Sat, 16 Dec 2023 11:04:08 GMT

Picture this: you're an indie dev, you've sunk 3 or more years of your life into making a game, and against all odds, you make it to release day. With must trepidation, you hit the launch button and all the download links become available. Eagerly you await the first reviews and comments from your fans. You start receiving messages and reviews. It's bugs! You scramble to fire up IDE, hoping to do a quick hotfix and fire that out before too many people start playing it, but you can't reproduce the problem. It must be something specific they're doing, but you don't have enough information to fix it.

Sound familiar? Following the indie gamedev community closely, I see a lot of these. Indie releases that don't have a good way to collect logs, and rely on players submitting bug reports manually, usually via screenshots sent to Discord or the Steam communities.

Fortunately, automating and receiving bug reports and debugging data is actually quite easy to do in gamemaker, and for those releasing games, this is very likely to be worth your while to do.

In the previous blog post I talked about structured logging, and made two points:

  • Logs should be a permanent part of code, and not just added temporarily to help debug something
  • Logs should be structured so that we can have other programs be able to read those log messages

The biggest reason for both of those things is that both of these are important for automated bug reporting. Having logs as permanent part of code helps generate the debugging info that may help you understand what was going on in the game just before the crash, while structured logging helps you make that data consistently formatted and searchable.

I'm spending this blog post talking about how to set up Sentry to serve as a hub to receive logs and crash reports that your game may generate. You can grab a copy of the script from my Sentry library over here. Grab the downloadable yymps file from the latest version

Releases · meseta/gmlogging-suite
Contribute to meseta/gmlogging-suite development by creating an account on GitHub.
Automated Bug Reporting with Sentry for GameMaker games
Automated Bug Reporting with Sentry for GameMaker games

Note: this contains the same Logger from my structured logging page. If you import both, you will have duplicates. It is safe to delete Logger.gml and use this instead.

Sentry as a bug handling tool

There are lots of bug report tools out there, the one I'm going to talk about is sentry.io. Sentry is a web service (it has a free account you can sign up to, as well as an open source release if you wanted to run your own) that serves as a command center for bug reports. You can write code in GameMaker that intercepts crashes, and instead of just displaying it on the player's computer, it can send that report over to your account on Sentry, for you to look at later.

And, if you are smart about logging, this report would contain not only the crash message, but a record of everything that the game was doing up to that point.

An example of a crash report looks like this:

Automated Bug Reporting with Sentry for GameMaker games

Let me explain what all these things are:

Tags

This top part of the report shows a bunch of "tags". Tags are key/value fields that allow you to group reports by OS, type, version, and even user. If you use my Sentry library, it'll automatically fill some information in here like the detected OS and runtime version, and the game's release version. But you can add more information here if needed.

Automated Bug Reporting with Sentry for GameMaker games

The Release version is important, so you can make sure common crashes are being fixed by subsequent releases

Crash and Stack Trace

Of course the main thing we want to know about the crash is the error message and stacktrace. This part of the report contains both, and it's the same message you'll get from the GameMaker crash dialogue

Note: the context lines (the line number and line that the code is on) isn't always available post-release due to the way GameMaker strips debugging information. So depending on how you build the game, this may or may not be available

Automated Bug Reporting with Sentry for GameMaker games

Sentry is smart enough to group together similar crashes, so you'll be able to get a quick overview of which crashes are common, and which are rare from your players

Breadcrumbs are an underappreciated feature of logging. If you use my Sentry library, every time you use the logger (stuff like logger.info() ) it'll save that log line to an array, ready to be included in a report if it crashes. This will give you some important contextual information about what might have been going on in the game just before the crash.

Automated Bug Reporting with Sentry for GameMaker games

Build and OS info

At the very bottom of the report is a bunch of build and OS information that gives you a bit of extra information about the report. If you use my Setnry library, this stuff gets added automatically.

Automated Bug Reporting with Sentry for GameMaker games

Issue list

While the above is what an individual bug report looks like, that's not the only benefit of using bug handling tools like Sentry. The other benefit is you get a nice overview of all the reports that are coming in.

Here's an example of that in action: this is a screenshot I like to share (with permission) from the game Forager, which used an early version of my library.

This was a view of their bug list a week into their beta test release. It showed that their top bug got 2000 submitted reports across 731 users, but importantly, if you look at the frequency histogram, every one of those bugs except for the third in the list was fixed by a release that came out 2 days before the screenshot was taken.

Automated Bug Reporting with Sentry for GameMaker games

Forager also went further, and I helped them modify my Sentry library to also package the player's savefile together with the report, so that they could load the file and investigate the player's save to see what was going on.

Setting Up Sentry

To start collecting bug reports like this, first step is to go register for an account at sentry.io. Then, when it comes to creating a new project, you'll see the following:

Automated Bug Reporting with Sentry for GameMaker games

Unfortunately GameMaker isn't on the list, but also it doesn't matter a whole lot, since the platform mainly changes the icon in your project list, it doesn't change much else. For the irony factor, I'd say go with Unity.

In the next screen, it'll show you your DSN. This is the unique ID for your project which you need to use to make sure that reports submitted from your game end up in your account. Copy this. This value is also available in the project settings should you need it later.

Automated Bug Reporting with Sentry for GameMaker games

Setting Up Your Game

Once you have a sentry account, grab my Sentry logging library, here is the link again:

Releases · meseta/gmlogging-suite
Contribute to meseta/gmlogging-suite development by creating an account on GitHub.
Automated Bug Reporting with Sentry for GameMaker games

Download the .yymps file, and from your gamemaker project use "Import Local Package"

Automated Bug Reporting with Sentry for GameMaker games

Once you do that, this is how you set it up. Put these somewhere early on in your project.

// create a sentry instance
global.sentry = new Sentry("https://<snip>.ingest.sentry.io/<snip>");

// set gamemaker to use sentry's exception handler
exception_unhandled_handler(global.sentry.get_exception_handler());

// create the root logger
global.logger = new Logger();

// Tell the root logger to send breadcrumbs and stuff to sentry
global.logger.use_sentry(global.sentry);

Now, as the game is running, and you use the logger (global.logger, or any child created from it), the log messages will be buffered as breadcrumbs, and whenever your game crashes, the Sentry handler will be invoked, and the player will be asked if they want to submit a crash report that'll include these log messages, and the error message. Hitting Yes will fire off the bug report.

Automated Bug Reporting with Sentry for GameMaker games

This is the default behaviour of my Sentry library, but there are several things you can tune/adjust.

Advanced usage

The Sentry library has a few values that can be used to customize it, see the self._options value inside it. you can adjust things like turning on and off the dialogue to ask whether to send the bug report (if you want to always send it automatically without warning the user). You can turn on and off the stacktrace. You can customize the message, and so on.

global.sentry.set_option("ask_to_send", false);

You may also want to set custom tags. These can help you filter your logs

global.sentry.add_tag("release_mode", "demo");

If your game has user accounts or a way to identify a user, you can set the user information. This user information gets attached to Sentry and helps you figure out how many people are experiencing a particular error. This can be done at any time

global.sentry.set_user("abc123");

In addition to automatically sending bug reports, you can also manually send a report. This is useful if you want to send a non-crashing error that you want to be informed about.

global.sentry.send_report("error", "Hello this is a report");

This will show up on the Sentry dashboard just like all the other crashes.

There's more information about how to use this library over in the Wiki:

Home
Contribute to meseta/gmlogging-suite development by creating an account on GitHub.
Automated Bug Reporting with Sentry for GameMaker games

So, there you have it, a way to automatically collect crash reports for your released GameMaker games.

I really encourage people releasing GameMaker games to use some way of easily collecting bug reports. If you have a game you're releasing and would like my help to make sure that your game has a way to collect bug reports, reach out to me on Twitter or find me on the GameMaker discord

]]>
<![CDATA[Structured Logging in GML]]>https://meseta.dev/libraries-logging/65760394ddf5fa000160dc26Tue, 12 Dec 2023 19:06:18 GMT

Ah, show_debug_message(), how can we live without you? It’s bread-and-butter for debugging.

However, it’s a mess. If you add that everywhere without cleaning it up, your console output is going to look like word salad as a stream of random numbers and strings like “here” or “aaaaa” or “asdfsdfsdf” you threw in there at random points during debugging comes out. Then, as soon as you tidying up and delete all the debug messages, thinking you're done with them, suddenly another bug happens that need you to put those back.

Fortunately, there’s more you can do with logging than just show_debug_message(), including different log severity levels, structured logging, the whole caboodle. I’ll try and cover a few of the ideas behind better logging that can help you organize your logs better. Particularly important if you start using GameMaker as a server, or to automatically collect crash reports.

The link below is the logging constructor I use for my own projects that encapsulate some of the ideas here.

GML Structured Logger
GML Structured Logger. GitHub Gist: instantly share code, notes, and snippets.
Structured Logging in GML

Debug messages as a permanent feature of code

I believe that a lot of debug messages should be a permanent feature of the code. Not just added when things go wrong, and commented out later. You might need your logs not just in the moment of debugging, but maybe later on, if only to confirm that some old code that broke in the past didn't break again. So why not make the debug messages a permanent feature of the code? Instead of treating show_debug_message() as something you throw in temporarily to be removed later, enshrine it as a permanent part of your code, and give it the functionality needed to allow you to easily manage it and make it useful without drowning you in random debug messages all the time.

The simplest example is: use a macro to enable/disable logging. This way, using GameMaker's configuration selector, you can globally turn on and off all log messages

#macro LOG_ENABLED false
#macro testing:LOG_ENABLED true

function log(_message) {
  if (LOG_ENABLED) show_debug_message(_message);
}

This is very rudimentary, of course, but this is just the start, and it’s the foundational concept: debug Messages are logs about how your program runs, and a permanent part of your code (as permanent as any other code anyway).

Named Logging instances

Ok, so we have permanent logging in our code, this is great if we needed all those logs, but it’s still a mess if we have a lot of it. So we need to be able to individually control different sets of logs so that we can disable what we don’t want to see, and enable what we do want to see. That way, if you’re debugging the player’s state machine, you can turn those logs on, and turn off all the other logs that aren’t relevant.

To do this, we need to treat groups of logs together, rather than have singular calls to a generic log() function. We can make use of constructors to create logging instances:

function Logger(_name="Logger", _enabled=true) constructor {
  self.enabled = _enabled;
  self.name = _name;

  static log = function(_message) {
    if (self.enabled) show_debug_message($"{self.name}: {_message}");
  }

  static set_enabled = function(_enabled) {
    self.enabled = _enabled;
    return self;
  }
}

With this, we can create individual logger instances for different objects or systems, each with their own set_enabled() functions. We also have the start of some custom log formatting (in this case, the string name is added to the front of every log message so that you can see which logger produced the message).

The use case would be: let’s say you have a player object which has its own distinct set of logs it needs to produce, like state transitions, or player state, or item pickups, or whatever. We can create an instance variable containing our logging instance that can be separately configured to any other logging instance in the game.

logger = new Logger("Player");
logger.log("hello")

// output:
// Player: hello

If you want to silence your player logs, simply turn it off by changing the default enable value it starts with

logger = new Logger("player", false);
logger.log("hello")

// no output!

If you create a more elaborate system, you could also toggle the enable state in code. Perhaps you have a debug overlay that has buttons to turn on and off different loggers, you can use the .set_enabled() method to do so.

Log Severity Levels

Not all log messages are created equal. Your average “aaaa” log message isn’t as important as a message that tells you about the change in some state you want to care about, and both aren’t as important as a log message that tells you something abnormal has occurred that you probably want to look into.

So, logs should have severity levels to help you pick out the important messages. And logs should be able to be silenced based on their severity. For example, in the above section, we had a logging instance that’s specific to the player object, which you could enable or disable to turn on and off all logging for that object. But what if you wanted to disable the spammy debug logs that you don’t need at this moment, but still want the more important logs? You need log severity levels.

It’s common throughout programming languages to have at least four different log severity levels:

  • Debug. These logs are to help debug a particular feature, and could be quite spammy. For example log messages describing how a particular algorithm is playing out. Typically you’d only ever want to see these if you’re actively debugging a particular functionality it is relevant for, and turned off the rest of the time.
  • Info. This is your generic log level, giving you info about the normal running of code.
  • Warning. This is for abnormal conditions that you, the dev, should be aware of. You’d use it for potential issues that may need corrective action, but isn’t code-breaking at the moment. For example, a controller object not existing but the game was able to spawn the object to avoid issues.
  • Error. This is for abnormal conditions that prevent the normal/expected operating of code. It doesn’t necessarily mean the game crashes, but rather it cannot do a thing it was intending to do. For example, loading a save file that was corrupted. In this case the result is the file cannot be loaded, so the game could not do the thing that it was being asked to do.

In some languages and logging systems, sometimes you’ll see a Trace level which is even debuggier than Debug, you’d use this even for debugging the values in a loop. Sometimes you’ll see a Fatal and even Critical level at the other end, which is like Error, but probably results in the program crashing immediately afterwards.

So we could improve our logging instance with support for the different log levels. (By the way don't copy this, the code linked at the start of this article has this in its completed form, this is just an intermediate stage to illustrate the progression from a simple to a more complete logging system).

function Logger(_name="Logger", _log_level=undefined) constructor {
  self.name = _name;
  self.__enable_error = false;
  self.__enable_warning = false;
  self.__enable_info = false;
  self.__enable_debug = false;
  self.set_level(_log_level);
  
  static debug = function(_message) {
    if (self.__enable_debug) self.__log(Logger.DEBUG, _message);
  }
  
  static info = function(_message) {
    if (self.__enable_info) self.__log(Logger.INFO, _message);
  }
  
  static warning = function(_message) {
    if (self.__enable_warning) self.__log(Logger.WARNING, _message);
  }
  
  static error = function(_message) {
    if (self.__enable_error) self.__log(Logger.ERROR, _message);
  }
  
  static __log = function(_level, _message) {
	  show_debug_message($"[{_level}] {self.name}: {_message}");
  }

  static set_level = function(_minimum_log_level) {
	  self.__enable_error = false;
	  self.__enable_warning = false;
	  self.__enable_info = false;
	  self.__enable_debug = false;
	  switch(_minimum_log_level) {
		default:
	    case Logger.DEBUG: __enable_debug = true;
	    case Logger.INFO: __enable_info = true;
	    case Logger.WARNING: __enable_warning = true;
	    case Logger.ERROR: __enable_error = true;
	  }

    return self;
  }
  
  static DEBUG = "debug";
  static INFO = "info";
  static WARNING = "warning";
  static ERROR = "error";
  static FATAL = "fatal";
}

Here, we include several statics to help indicate log level, but we also provide separate logging methods like debug() or info(). This is because the severity is intrinsic to the log message. There isn’t a situation where sometimes you want a “Set n=5” debug message needs to be a warning level, and so there’s no need to parameterize the level. The usage is now:

logger = new Logger("Player");
logger.debug("hello")
logger.warning("world")

// output:
// [debug] Player: hello
// [warning] Player: world

logger.set_level(Logger.WARNING);
logger.debug("hello")
logger.warning("world")

// output:
// [warning] Player: world

Now, our logger instance can be adjusted for severity, so we can leave most loggers set to the Info or Warning level, while the loggers relating to the thing we're actively working on set to the Debug level.

Structured Logging

While logs are mainly for humans to eventually read, that doesn't mean we can't benefit from standardizing the structure of our log messages. While the message show_debug_message($"a thing happened, my ID is ${id} and my state changed to {state}") is human-readable, it's not always easy to search for if you don't remember the words that were used.

Another benefit of standardizing the log structure is that it makes them machine-readable. Perhaps you want to automatically fire off bug reports so that the player doesn't have to copy/paste error messages into your support forums (something I'll talk about in the future in another post), or perhaps you're running GameMaker on the server and have a way to centralize these logs, and filter them by severity or keyword.

One way to standardize logging is by treating it as structured data. For this reason some people call it "json logging". Instead of just logging the tsring "Player object with ID id changed state from xyz to abc", we would log something more like "State change", {player_id: id, start_state, end_state}.

The code to do this is now too big for me to reproduce in the blog post, but take a look at the full logger that I use in some of my projects that include this feature (this is the same link as the start of the article):

GML Structured Logger
GML Structured Logger. GitHub Gist: instantly share code, notes, and snippets.
Structured Logging in GML

Here's a comparison of what structured logging looks like versus regular-old logs:

// Without structured logging:
logger.info($"Player object {id} state change from {start_state} to {end_state}");

// With structured logging:
logger.info("State change", {player_object_id: id, start_state, end_state});

When we do structured logging, we have a much shorter message to describe what it is that we're logging, in this case a "State change", and we accompany that with all the data relevant to it, in this case the player object ID and the start and end state.

It takes some getting used to writing logs like this, but the result is often cleaner, better structured, and plugs into logging and log-related tools better. For example, if you deploy GameMaker to certain servers that have a centralized logging facility, where all the backend logs are centralized for you to search, using structured logging means you can easily query and visualize the log messages, so they look like this:

Structured Logging in GML

Instead of this:

Structured Logging in GML

Bound values

Structure logging allows us to include arbitrary pieces of data into each log message, however, it’s often the case that you always want certain data to be present in all log messages made by a particular logging instance. For example, you might want a player object's logging instance to always include the instance ID, to avoid having to manually include the data in every log output. We want to be able to “bind” these values to the logging instance.

To do this, the Logger constructor could be updated to include these extra values at the moment of creation, and store them internally so that they can be included in each log output. This is why we wanted to have logger instances; each logger instance can hold onto values like this.

See the my logging code for what this looks like (again, this is the same link as the start of the article).

GML Structured Logger
GML Structured Logger. GitHub Gist: instantly share code, notes, and snippets.
Structured Logging in GML

The usage looks like:

global.logger = new Logger(); // the root logger

// Somewhere in a player object
logger = global.logger.bind_named("Player", {player_id: id});
logger.info("hello")

The child logger inherits all of the bound variables of its parent. You might even want to create short-lived child loggers just so that you can bind some extra variables to it.

Considerations

The more elaborate our logging process, the more likely it is that someone brings up the "b...but performance" question. Yes, structured logging and more elaborate logging systems do have a performance implication, and it is going to be something you will want to balance in terms of performance versus utility. My opinion is that most of the time logging isn’t a huge hit, and if you do have a thousand objects all streaming out logs at once, you will have an issue, and sometimes you will want to keep the Debug scope logs at a minimum.

But, the great benefit of this type of logging is you can easily find that in your code, and remove and refactor as necessary. As with all things, a good clean abstraction is one that is just as easy to add as it is to remove. And so implementing logging like this gives you options: you can easily remove and refactor it as you see fit when you start having to consider performance.


Throughout this article, I’ve talked about the different shades of logging you can do, ranging from the bare-bones "global logging function that can be turned on and off", to the much more involved "instanced structured logger, that can have values bound to it" whose power only becomes apparent once you start plugging in different ways to consume those logs. Like automated bug reporting!

]]>
<![CDATA[MinHeap Priority Queues, an alternative for ds_priority]]>https://meseta.dev/libraries-minheap/65766696ddf5fa000160dcadMon, 11 Dec 2023 18:20:52 GMT

Since GameMaker 2.3, I've started using the ds_ functions less and less, with their old-school memory management requirements and managing numeric indexes, and all the bugs that can come from that.

The alternatives for ds_map is clearly a struct; and ds_grid, ds_stack and ds_queue are also achievable using arrays in a relatively trivial way. But ds_priority is one of the more complex ones to replace since under the hood it's not just a straightforward data structure, it needs a few extra bells and whistles to work.

Long story short, I now just use my own constructor-based replacement for ds_priority that implements a Heap under the hood. Here it is for everyone who wants a more modern priority queue that is GC'd:

GML MinHeap implementation
GML MinHeap implementation. GitHub Gist: instantly share code, notes, and snippets.
MinHeap Priority Queues, an alternative for ds_priority
GML MaxHeap implementation
GML MaxHeap implementation. GitHub Gist: instantly share code, notes, and snippets.
MinHeap Priority Queues, an alternative for ds_priority

What is a ds_priority?

A ds_priority is a datastructure that helps keep track of items and priorities, often referred to as a "priority queue". You can insert value-priority pairs into it in any order you want, and at any time you can query for the lowest or highest priority value in the list.

This is useful for things like correctly ordering your UDP netcode packets which arrive out-of-sequence. Or taking a list of characters with different initiative values and figuring out their move order. Or it's the core of graph and path-finding algorithms such as A*.

From a computational-efficiency point of view, it is a "sort-on-insert", meaning all the computational power necessary to make sure things are order are incurred at the moment of inserting the value. In games, this is quite good for situations where you gradually add values to the ds_priority since you spread out the computation power of sorting; so that when you pull values out of it later, you incur less processing power, which means fewer lag spikes. This is in contrast of the opposite approach of inserting into an unsorted array (which is very fast), and then having to sort the array when you need to use it, which is slow and can incur a lag spike when you do it.

The Heap data structure

I won't get into the details of how this works, but suffice to say that a Heap is a way of structuring data in a way where the priority-order is maintained. ds_priority is almost certainly a Heap data structure under the hood. You can find out more about the Heap data structure on Wikipedia:

Heap (data structure) - Wikipedia
MinHeap Priority Queues, an alternative for ds_priority

MinHeap vs MinMaxHeap

The main difference between my MinHeap implementation and ds_priority (aside from MinHeap being implemented as a constructor and arrays, meaning you don't have to worry about memory management) is that ds_priority is a MinMaxHeap, in that it keeps track of both the minimum and maximum priorities of the heap. You can easily read and remove either the minimum priority item or the maximum priority item at any given time.

My MinHeap and its brother, MaxHeap implementation only does one of these: MinHeap lets you only remove the minimum priority value item from the heap, and doesn't have a way for you to look at the maximum priority. And similarly MaxHeap only lets you remove the maximum priority value item.

It is possible to write a MinMaxHeap implementation, I just haven't done it. I haven't come across a situation where both are needed yet. I've only seen use-cases where you want one or the other. But perhaps this will change, time will tell.

MinHeap Usage Example

Using the code linked at the top of the article, minheap usage looks something like:

turn_order = new MaxHeap();
// insert a bunch of values and their priorities
turn_order.insert(PlayerA, 4);
turn_order.insert(PlayerB, 6);
turn_order.insert(PlayerC, 2);

// Who has the lowest priority?
var _highest_priority_player = turn_order.peek_max_value();

// Process players in turn
while (turn_order.get_length() > 0) {
  var _player = turn_order.pop_max_value();
  process_turn(_player);
}

If you're looking for a more modern way to make a priority queue, use a Heap implementation like this one, and free yourselves from the tyranny of having to memory-manage ds_priority

]]>
<![CDATA[Exception Inheritance in GML]]>https://meseta.dev/gamemaker-exceptions/6575afdcddf5fa000160db0fSun, 10 Dec 2023 15:13:52 GMT

GameMaker has try/catch exception handling, which is useful any time you deal with code that have complex failure modes that you can’t realistically be checked for. But it's also, like anything in programming, a potential pitfall. I want to dedicate this article to talking about what to look out for, and how to make the most out of exception handling in GML, and some techniques other languages use to make this an expressive tool rather than a clumsy one.

Before I get into that, I have to first note that the well is deep when it comes to programming philosophy and ideological debate about whether you should use try/catch versus other ways of writing error handling. Many people will say that try/catch exception handling is the literal demon scourge, and that if you use it you are bad and should feel bad. Some of the key arguments include: if you know there’s a way for your code to raise an exception, you should simply just check for it since it is no longer an exception; or that try/catch introduces another path for the program to flow, making it hard to reason about; or that try/catch is slow (that's a weird myth, in GML, it's actually fast and potentially faster than alternatives!)

However, I believe that at least for now, because of the way GameMaker's built-in functions work, and because of a lack of language features like destructuring, it is a pragmatic choice to use try/catch exception handling while writing GML, despite the drawbacks that people describe, and there are a few things you can do to avoid the pitfalls and make it useful.

Crashing is good, actually: Overbroad catching

The most important thing to watch out for when using try/catch is the fact that it will catch any and all exceptions, including a lot of errors that you ideally shouldn't be catching. This is called over-broad error catching, and what makes try/catch an inaccurate tool if not used carefully.

For example, let's take a case where try/catch is often needed: json_parse(). GameMaker's json_parse() function doesn't have a sentinel return value for when the input is invalid JSON. Instead, it'll throw an exception. The only way to prevent this from crashing your game is to do json_parse() inside of a try/catch.

So, let's say you have the following code:

try {
  var _data = json_parse(_string);
}
catch (_err) {
  // do something
}

Looks straightforward enough, right? Surely not much can go wrong?

Here's a problem: this catches everything. Even for trivial code like this, there are at least two errors possible that you probably don't want to catch: if _string doesn't exist as a variable (maybe you made a typo), and the more obscure problem of if _data is a function parameter, which means you cannot declare it again as a local variable with var _data. Imagine if you were doing more stuff between the try and the catch, there's way more that could go wrong that you would be accidentally catching.

Both of these errors, if you make them, will be caught by try/catch and unless you are careful about how you display the error message, you may end up hiding these bugs from yourself: you've made the game not crash when the JSON is invalid, but you've also made the game not crash when you have an actual bug in your code; a situation where you actually want the crash to happen to inform you of a code problem to fix.

So try/catch is a clumsy tool by itself. It's over-broad. And the thing that you should keep at the front of your mind when writing a try/catch is: how do I make this as specific to the exact problem I want to catch as possible?

Catch and rethrow

The throw keyword lets us throw exceptions when we want the code to signal an abnormal situation that it can’t handle. These are what are triggering the catch to happen. json_parse() is doing that throw internally, but you can also throw in our own code too, and importantly, you can also rethrow inside a catch. This allows us to catch an exception, examine it to decide whether it’s something the code can handle, or rethrow it again if it isn’t to allow some outer scope to handle it or crash the game.

For example:

try {
  do_something();
}
catch (_err) {
  if (is_struct(_err) && _err[$ "message"] == "JSON parse error") {
    // handle it
    show_debug_message($"whoops {_err.message}");
  }
  else {
    // rethrow it
    throw err;
  }
}

Here, we examine the format and contents of _err to see if it is the specific thing we want to catch, and if it is, we "handle" it (for the purpose of this example, just showing a debug message), and everything else we don't want to catch we re-throw so that something further up the call stack might handle it, or let it crash the program if nothing wants to catch it.

GameMaker's built-in json_parse() function will throw an error struct that looks like this:

 { stacktrace : [ "gml_Script_anon@1204@gml_Object_obj_demo_Other_4 (line 39)","gml_Object_obj_demo_Other_4 (line 46) - a();
" ], script : "gml_Script_anon@1204@gml_Object_obj_demo_Other_4", line : 39, message : "JSON parse error", longMessage : "ERROR in
action number 1
of Other Event: Room Start
for object obj_demo:


JSON parse error
 at gml_Script_anon@1204@gml_Object_obj_demo_Other_4 (line 39) - 		json_parse(_string);
" }

So if we want to catch just this exception and no others, we can have our catch code inspect the exception for the correct message property, and rethrow everything else.

This is fine and great for dealing with GameMaker's built-in errors. But as always, there are ways for us to use a similar but better thing for our own functions and libraries.

Throwing structs with constructors

In the above section, that struct that was being thrown was a struct generated by GameMaker's built-in functions. We don't have a lot of options when it comes to those, we just have to handle whatever GameMaker generates. But for our own code and libraries, we get to choose what we throw. We can throw any type of variable: a string, a number, a struct, and others.

throw "hello";

throw -1;

throw {error: "oops"};

The third example above will cause the error in the nearest catch to be the struct {error: "oops"}. And of course, if we can throw structs, we can also use constructors to create these structs.

For example, let’s say we defined our own constructor for exceptions:

function Execption(_message) constructor {
  self.human_friendly_message = _message;
}

Then we can catch it and inspect its values:

try {
  throw new Exception();
}
catch (_err) {
  if (is_struct(_err) && is_string(_err[$ "human_friendly_message"])) {
    // the exception is one of our custom human-friendly ones
    show_alert_to_user(_err.human_friendly_message)
  }
  else {
    // rethrow
    throw _err;
  }
}

Of course, it's not very useful to just try { throw }, but imagine that it was some more complex code in there or function calls and there's a throw or two somewhere deep inside.

We've defined a constructor that can be used to construct exception structs in exactly the format that we want. We can use it to store whatever information that is necessary for us to handle the exception later (like a nice human-friendly messages to show to the player), and it could also include other information that can help us handle the exception.

Exception inheritance

In the above examples, we check that the exception is a struct with specific properties, using a combination of is_struct() and other checks. But we're not limited to doing this. Because we're throwing using a constructor, we can use GML's constructor inheritance checking function is_instanceof()

try {
  ...
}
catch (_err) {
  if (is_instanceof(_err, Exception)) {
    // the exception is one of our custom human-friendly ones
    show_alert_to_user(_err.human_friendly_message)
  }
  else {
    throw _err;
  }
}

Using is_instanceof lets us check that the struct came from a specific constructor, or any of its children. That latter point is important: it means we can check for child constructors whose parent is Exception.

For example, let's take the hypothetical situation described in the previous Sentinels post, which returned a special sentinel values when an equipment slot doesn't exist. Instead of returning a sentinel, we could throw an exception instead (which may be a cleaner way to handle that particular situation than using a sentinel return value). We could define a child exception to the custom Exception constructor, and bake-in the error message to avoid having to type it out every time.

function NoSlotException(): Exception("That slot doesn't exist") {};

Now, every time we need to alert the user of the code of a no-equipment slot exception, we can throw new NoSlotException() (without needing to provide the actual text), and the error message it produces will be standardized. The end-user of the code can then check for this specific exception using an is_instanceof()

try {
  ...
}
catch (_err) {
  if (is_instanceof(_err, NoSlotException)) {
    // that inventory slot doesn't exist, so skip it
    draw_empt_slot();
  }
  else {
    throw _err;
  }
}

Exception Inheritance Trees

Being able to not only test for specific exceptions, but also children of specific exceptions is extremely useful when writing and using libraries, since very often you (the library author) don't know ahead of time exactly how the code will be used, and what exceptions might be okay and what exceptions are not. By throwing exceptions that the end-user of the code can check for, it can be left to the end-user of the code to handle exactly the ones they want to handle and let crash the ones that should crash.

For example, a library written to handle save file loading could detect and throw multiple types of exception, such as:

  • the file not existing
  • the format being unreadable
  • the expected values being missing
  • the save version being incorrect

These might all need to be handled in a different way by the end-user of the library, but at the time of writing of the library, you don’t know for sure. So you could make a tree of exceptions using constructor inheritance, throw these at the relevant parts of the library code, and let the end-user check for them:

// our base exception for the savel/load library
function SaveSystemException() constructor {}

// base exception for file-related issues
function SaveSystemFileException(): SaveSystemException() constructor {}
function SaveSystemFileNotFoundException(): SaveSystemFileException() constructor {}
function SaveSystemFileUnreadableException(): SaveSystemFileException() constructor {}

// base exception for format-related issues
function SaveSystemFormatException(): SaveSystemException() constructor {}
function SaveSystemFormatExpectedValueMissingException(): SaveSystemFormatException() constructor {}
function SaveSystemFormatSaveVersionException(): SaveSystemFormatException() constructor {}

The end-user could opt to check only for the base exception, or they may want to implement different behaviours based on the exact exception being thrown. Whatever works for them.

We could even go further, and inside the save file loading library, we could help the user out by translating a GameMaker exception into our own. So that original json_parse() exception handling from earlier on, we could re-write inside our library to rethrow a custom exception instead of the default GameMaker one:

try {
  _data = json_parse(_string);
}
catch (_err) {
  if (is_struct(_err) && _err[$ "message"] == "JSON parse error") {
    // convert into a custom one and rethrow
    throw new SaveSystemFormatException();
  }
  // rethrow everything else
  throw err;
}

More Resources

Here's a Gist from Nomm, who has some custom exception constructors that you can use in a similar way to described in this post, with extra features.

Exception class for GameMaker 2023, allows custom message or wrapping runtime errors. Allows for error script to be loaded into memory
Exception class for GameMaker 2023, allows custom message or wrapping runtime errors. Allows for error script to be loaded into memory - Exception.gml
Exception Inheritance in GML

I'm not just making up these techniques. They're the same ones used by JavaScript, Python, and other languages. Python in particular has an extensive exception tree, which it not only uses in its standard libraries, but also there is a well-established convention that Python library writers follow, to make use of the standard exceptions in their own code. For example, you can reasonably assume that a python library someone wrote that handles files would throw a FileNotFound error for situations of that description.

Exception Inheritance in GML
Example exception tree that's provided by Python's standard (built-in) library

Now you know: you don't have to just throw a string, you can throw whole-ass structs with constructors; and by checking for the specific exceptions, GameMaker's try/catch exception handling can be used to give your library or game code more expressive ways to handle exceptions.

]]>
<![CDATA[Sentinels and Abstract Base Constructors in GML]]>https://meseta.dev/gamemaker-sentinels/656d53895218f000088f1c20Mon, 04 Dec 2023 04:20:25 GMT

When it comes to returning values that are meant to indicate the absence of a thing, GML frequently uses sentinels values like noone, undefined, and -1 are frequently used. But there are better ways for your own code!

GML's use of sentinels is notoriously inconsistent. Largely owing to GML's long history and stalwart support for backwards compatibility, it's built up lots of functions that all have slightly different conventions for a "null return". Functions like instance_place() this will be noone, but functions like struct_get() it'll be undefined instead. Other times, -1 is used, and for string functions 0 will be used instead. If you want to get really into the nitty gritty of GML and its weird and wonderful quirks, you'll even find the odd -100 being used by object_get_parent() as a sentinel value.

Sentinels and Abstract Base Constructors in GML

While we simply have to put up with GML's fast and loose use of sentinel return values, we have the freedom to pick our own return values for the functions and libraries that we write. We don't have to stick with noone, undefined or -1 if we don't have to. Let's explore what we can return when there is nothing to return.

Undefined as a sentinel

Let's start with what might be the default choice of return value for things that don't have a return value: undefined.

The defining characteristic of this sort of sentinel is that it's a value you can check for, and it won't be confused for anything else. Take for example -1 , which fulfils this definition but only for index values, because indices cannot be negative, so if you see a -1 returned by a function that normally returns index values, you know it must means something else. Similarly noone (equal to -4) fulfils this definition, because when you expect the return to be an instance or object.

However, undefined is a bit different. There are instances where it's more ambiguous, take for example struct[$ "foo"]. If this returns undefined, that could either mean "foo" does not exist in the struct struct, or it could mean the value of struct.foo is holding the literal value undefined. So here undefined is ambiguous when used as a null return, because we can't tell if it's the absence of a value or simply the actual value.

Take for example an equipment slot system you build where you want to get what equipment is in the character's equipment slot with equipment.get_slot("arm") and this returns undefined. Does that mean there's no equipment in the arm slot, or does it mean that the arm slot itself doesn't exist on this character? It's ambiguous.

Sentinels and Abstract Base Constructors in GML

(Note: It's entirely valid to do a check to see if a slot exists here before drawing it, and I'd actually argue that some solutions like that are actually better ways to solve this particular problem, however for the purpose of demonstrating sentinels in this blog post, let's roll with using the return value instead.)

If at some point you accidentally forget to handle the undefined, then you have an error that looks like:

Variable <unknown_object>.draw(100573, -2147483648) cannot be resolved.

This sort of error is pretty annoying to look at, and sometimes if you propagate that _arm value deep into other code before this error manifests, it can be difficult to trace back and figure out that sometime earlier you got an undefined value.

This, by the way, is one of the reasons why people say "null values are bad", or more dramatically "nulls are the worst mistake in computer science", like ok, fine, but no use telling us that now, where were you when computer science was invented?

Primitives as Sentinels

So, the value noone is equal to -4, that's an example of a number being used as a sentinel. We could come up with our own value to use, and that's equally good/bad compared to using noone

Sentinels and Abstract Base Constructors in GML

Here, we're using a static on the Equipment constructor to keep the value nicely namespaced, but we could equally use a #macro NO_SLOT -99 instead. Two different ways of achieving the same thing but with slightly different impact on code organization. Sometimes one is better than the other.

It does feel a bit weird using some arbitrary value as a sentinel, but from a technical point of view, it's not particularly worse than using undefined, and if you accidentally let one through to the rest of your code, your error starts looking like:

Unable to find instance for object index -99
at gml_GlobalScript_init_site (line 32) - a.draw();

Which... actually isn't the worst in the world. The error type "unable to find instance" is misleading, but if you searched for -99 in your code, you'd find the NO_SLOT value. So it's a slight boon on debugging. We now have a sentinel that, though a bit weird and magic-numbery, is slightly more descriptive than undefined

We could take this further and use a string, let's say a static NO_SLOT = "NO SLOT DUMBASS"; as the sentinel, with the rest of the code looking the same, and the errors start getting a bit more helpful:

unable to convert string "NO SLOT DUMBASS" to integer
at gml_GlobalScript_init_site (line 32) - a.draw();

It's still weird to do it, the error type "unable to convert string" is misleading, but at least the string itself gives you further clues and probably even more unique than the number -99

Typing issues

One of the big issues with using primitives like strings and numbers as a return type is that they make the type interface more complex to look at. With Feather not particularly handling a situation like this very well.

For example, we could establish that the return type for get_slot() is Struct OR a String:

Sentinels and Abstract Base Constructors in GML

But, with something like this, Feather, at least for 2023.11 (let's hope it gets better), makes a variety of weird guesses as to the type depending on how you write the checks.

Sentinels and Abstract Base Constructors in GML

Oops, Feather thinks _arm is a string here, which isn't very helpful. In general, mixing types is not a good thing, and even if feather were better at guessing, this sort of thing brings more problems than it solves.

Structs as sentinels

So if we can't mix types, what if we return the same type as what we normally expect, and use that as the sentinel?

The primary criteria for a sentinel is a value that can easily be distinguished from the rest. Let's say we had a constructor Slot() that implements the interface for any equipment slot, we can actually just instantiate a new slot whose sole job is to represent an empty one. Just like we previously assigned a primitive to NO_SLOT, we can assign this sentinel struct:

Sentinels and Abstract Base Constructors in GML

And in doing so, our check against the sentinel value continues to work the same as before, we are just now checking against a struct reference. NO_SLOT is always the same struct since it's a static, so as long as we don't accidentally reassign that value, it will always remain the same reference to check against. (Note: in this case, you can't use a #macro, and the static define here is required to ensure that the value is consistent everywhere)

Sentinels and Abstract Base Constructors in GML

Importantly, Feather is now happy. It can correctly infer that _arm is a Struct.Slot, and it will bring up all the correct method definitions associated with it, even giving us an error if we try to do an invalid type operation

Sentinels and Abstract Base Constructors in GML

No crash!?

This now seems a lot better of a situation to be in, but it introduces a far more subtler set of problems: where before, returning a primitive sentinel made it extremely obvious when you tried to use the variable in a way it couldn't be used - you'd cause an error and crash, which is actually pretty vital during development since you want to know about these problems. Now, since our sentinel is itself a valid Slot struct, it can behave in all the ways that Slot structs can behave, including having a draw method. This means that if you accidentally forgot a check for the special NO_SLOT sentinel, you'd have a piece of code that didn't fail as expected.

So, crashing is actually important, and we want our program to crash when we try to do something invalid, because the alternative is a hellish purgatory where your code is apparently working fine, but it doesn't do what it should, and you have no error message helpfully pointing you at the place where things started to go wrong.

Sentinels that crash

The solution? Make it crash. Make the NO_SLOT sentinel-struct deliberately crash whenever it's used in a way that is not allowed. Have every possible method that NO_SLOT could feasibly contain just throw an error.

Sentinels and Abstract Base Constructors in GML

Now, NO_SLOT is an instance of the new NoSlot constructor that inherits from Slot, so it can be used as the same type (feather is okay returning a NoSlot where the function signature requires Slot). But any time you try to draw(), you'll get a crash, and you're free to put whatever error message you want in there that is maximally descriptive of how it fucked up.

The only downside is that the NoSlot constructor needs to be maintained carefully, because not overriding values can lead to continued confusion. Say you added an use() method to Slot but forgot to override it with a method that throws in NoSlot, then you have the same problem as before: a sentinel that doesn't error when it should.

Abstract Base Constructors

A modification to this approach is to turn the problem upside down. Instead of NoSlot inheriting from Slot and overriding every method with one that crashes, we can do the opposite, and start with a base constructor whose only job is to establish all the capabilities of a Slot, but to not implement any of them. In other words, an abstract base constructor.

Ok, I'll admit it. Abstract Base Constructor is a term I made up to sound smart. I'm modelling this after Abstract Base Classes from Python, which behave in a similar way. But the idea of abstract classes is common in other languages, though with slightly different semantics of their use, and so I feel justified in calling this something similar. It's a Base Constructor because other constructors inherit from it, and it's an Abstract Constructor because it doesn't implement any actual behaviours. So it's an Abstract Base Constructor.

Sentinels and Abstract Base Constructors in GML

The significant benefit of using ABCs is that not only can you use it as a sentinel, since every method on it will crash when used (you may make a child constructor called NoSlot for more clarity, since it's semantically weird to be instantiating an abstract constructor), but it also helps you ensure all of your child implementations, like Slot or perhaps if you have multiple different types of slot like ArmSlot or LegSlot that have slightly different implementations in them, all correctly implement the required methods. Because if you forget to implement one, and try to use it, you'll get a crash alerting you to the mistake.

This is now a much better sentinel than we had before, since it has all the desirable properties:

  • It is a distinct value and type that can be checked to distinguish it from valid data
  • Its type is consistent and simple
  • Using it in an invalid way results in a descriptive and not misleading error message
  • Low chance of the sentinel no longer being a valid sentinel if you forget to override a method.

Special mention: Null Structs/Null Constructors

The above focuses on Sentinels. Things that represent special things like missing values, which can be checked for, and which will crash if used badly. However, in the specific example given, the lack of an equipment slot, there is an alternative that is perhaps even easier. A null struct.

Because in a lot of cases, the intended behaviour when a slot isn't there, or when a slot is unoccupied, is to simply do nothing, we could drastically simplify our code and remove all the checks for NO_SLOT by having a constructor that implements all the usual methods, but have the methods literally do nothing. This is a Null-constructor, it's good for nothing, like that cousin your parents warned you about to scare you into doing schoolwork.

However, the tradeoff of not having to check for sentinels is that your null struct will go through whatever code and behaviours that a regular value will, and that could be a worse tradeoff than just doing the check. It's one thing to have a null struct with an empty draw() method, it's another to have a null struct go through a substantial amount of code that does exactly nothing just to avoid writing an if to check for a sentinel


I hope this has been useful discourse for the breadth of options when it comes to sentinels, and that undefined are not the only option, and there exists a range of different ways to express non-existence.

]]>