I have one month to make an MMO: Day 4

Yuan Gao (Meseta)
Meseta’s MMO experiment
7 min readAug 23, 2019

--

Wow, it’s been a really long day today. I had to put in a good 12 hours of work today to reach the goals for this sprint, making up for shorter days on Day 2 and 3. Plus, I got distracted for a couple of hours exploring some UI components that I should have left for much much later.

Why I use JSON-RPC

I just finished implementing the Python side of the login flow that was outlined in Day 2. This is where the Game Client, once it receives its login token, can now connect to the Python server, and use the token to prove that it has logged in.

All communication between the game client and the python server will use JSON-RPC. RPCs, or remote procedure calls, are methods by which one machine can make a call to a procedure (a function) on another machine over a network.

For example, the function to validate the token lives on the Python server. The game client needs to be able to call that function in order to validate its token, but the game client is on a totally different machine, and is written in a totally different programming language.

To solve this, we need to establish some means of communication between the server and client, over which the client can send requests for a certain function to be run on the server (and vice versa). We’re not transmitting the function itself, just the fact that a specific function that already exists on the server should be run, and with what inputs, and fetch the results of that function.

A major complexity in any networked activity is the fact that anything sent over the internet may take a while to reach its destination. The farther away the server, the longer this is. A request sent from London, to New York will take a good several tens of milliseconds to get there simply because that’s how long it takes light to get there in our under-sea optical fibres connecting the two locations. If you’re making a game, you don’t want the game to freeze for 70ms every time some network data has to make a round-trip. All of those delays add up, and would make the game feel choppy. The solution to this is to make the call asynchronous.

Just like as discussed in yesterday’s post, when something is asynchronous, they can happen separately to other tasks in the program. In the context of network operations, an asynchronous request means the request can be sent, then while the request makes its way to the server and back, the game can be doing other things like accepting user input, displaying a loading screen, or just regular gameplay, without having to pause to wait for a reply.

However, once a network operation is made asynchronously, there are no longer any guarantees that of several requests sent, the results come back in the same order. They may come back in totally different orders, or maybe sometimes not return at all. So each response must be routed back to the correct request so that the program knows how to correctly deal with the information. Fortunately, this is exactly what an RPC pattern allows you to do.

A JSON-RPC is simply an RPC scheme that uses JSON as the method for encoding the data. The primary reason behind picking JSON-RPC over any other RPC is the fact that GameMaker has a reasonably fast json_encode function for producing JSON. Using any other protocol would involve implementing some custom message building, which would slow us down.

A JSON-RPC request for say, a ping looks like this:

{"jsonrpc": "2.0", "method": "ping", "params": [1566499168], "id": 123}\n

The JSON-RPC structure includes:

  • a protocol version number(which strictly speaking isn’t necessary and probably isn’t doing anything useful, but is included anyway to be compliant)
  • the name of the method, which in this case is ping
  • the parameters which will be used as an input to the ping function. In this case a list/array with one value, the timestamp at which the ping was created.
  • the ID of the request. It’s this ID that will uniquely identify the result as being for this request.

Once the client has created and sent this JSON-RPC, it can continue doing all the other things it should be doing, while periodically checking to see if a response with ID of 123 has been received yet.

When the server receives this packet, it will run the function that is mapped to the ping method while passing in the parameters. And will collect the result and send back a JSON-RPC request

{"jsonrpc": "2.0", "id": 123, "result": 1566499168}

The response consists of:

  • The version number again
  • the ID of the response. This is the same as the request ID
  • result. The results!

Here’s a short, poor-quality, animation of using PacketSender, a tool for debugging network packets, to simulate a login request.

The sequence of events displayed is:

  1. Prepare RPC. This is just me manually typing in the RPC request to make a login to the server. It contains a secret token value received from logging in earlier.
  2. RPC Sent. The RPC is sent from my computer to the server
  3. RPC Received. The black window is the logs from the server, showing it receiving the RPC I sent, and processing it
  4. Token Cleared. Once the authentication token is used, it’s removed from the database to prevent token re-use. The window in the top right is a live-updating view of the database that stores the token, you can see it turn red and disappear from it, indicating that the token was removed
  5. Login success received. Once the token is cleared, the server sends a response back with a value of true to indicate that login was successful, it shows up as a JSON-RPC response on Packet Sender

There are a few other ways JSON-RPC can be used, including for sending notifications that don’t require a response; and for sending error codes when things go wrong. The simplicity and flexibility of an RPC system makes them a good choice for networked games. Perhaps the main alternative to RPC would be Message Brokers, which in fact is exactly what we’ll use on the server-side of things as it’s more appropriate there.

For those interested in using JSON-RPC in GameMaker, I’ve previously released an asset for this on the YYG marketplace, mainly for my own use. Partly for that reason it has little documentation. I’ll be using this very asset in this project.

The asset on the YYG marketplace

Here’s what some python code looks like that handle the RPCs:

This also uses my own asyncio-friendly JsonRpc library.

The Game Client, finally!

I’ve finally created the game client. With this, the triumvirate is assembled!

GML, Python, and JavaScript. The three languages that together rule over this project

The game client has a pair of objects responsible for this login sequence: an obj_login whose job is to go log in and fetch the token from the Javascript component over HTTPS. The easiest way to explain what it does is to look at its state machine enum:

obj_login state machine enum

Simply a set of states: one to collect user input; one to issue the HTTP request; one to wait for the response; and one to act on the received token. obj_login once it has a successful login, will spawn the second object, obj_connection to complete the next phase of connection.

The obj_connection’s job is to grab the server list, then make and maintain a TCP connection to the Python server, as well as sending the token. It will persist for the duration of the game session, and will also handles all the JsonRPC logic. It contains a transmit buffer that any other object can queue up RPCs inside; as well as structures for receiving RPCs where other objects can check for returned values or listen for requests.

The sequence of tasks can be seen reflected in the state machine enum: grab the server list; make a connection; send the login token.

obj_connection state machine enum

When the project is run, the debug output tells the whole story of the two objects doing their jobs to make a connection. The actual GM game itself is nothing interesting to look at, as it’s just a handful of debug get_string functions to get the username and password for now.

I’m not sure why GM produces an HttpError, it doesn’t appear to affect operation.

Day 4 Task summary

I’ve successfully managed to hit the first sprint goals on time, though I did have to put a few extra hours into it.

I’ll take a half day off tomorrow and gather myself for the next sprint.

--

--

Yuan Gao (Meseta)
Meseta’s MMO experiment

🤖 Build robots, code in python. Former Electrical Engineer 👨‍💻 Programmer, Chief Technology Officer 🏆 Forbes 30 Under 30 in Enterprise Technology