Multiplayer in Godot 4.0: On servers, RSETs and state updates

By: Fabio Alessandrelli 5 August 2021

Howdy Godotters!

It's time for the first update on Godot 4.0 multiplayer and networking changes.

In this post, I'll focus on the new "headless" display, and the removal of multiplayer RSETs (read below before despairing!), along with keeping you hyped with some of the new features planned or in the work.

Goodbye server platform, hello headless display!

One of the emergent feature of Godot that users have discovered and really started to like is its ability to run on a headless Linux machine and act as game server.

Over time, macOS support was added to the server platform, and users showed interest in a windows server.

Finally, in Godot 4.0, you will be able to run Godot with headless display (no rendering) on any desktop platform.

Passing the --display-driver headless command line argument will start Godot without rendering, like the old server platform, no matter if you are on Linux, macOS or Windows.

Additionally, we are working on a series of "server" export options, to let developers further reduce the memory and load time footprint by replacing specific assets with "fake" files that only contains metadata. For example, a "fake" audio file will not contain any audio, but will retain informations about length, loops, etc. This means that server-side code relying on audio timing or texture sizes will still work correctly.

Removing RSETs

When building the multiplayer API, two forms of network communications were introduced: RPCs to call functions remotely, and RSETs to set variables remotely. This seemed like a good idea at the time: RSETs were a fast way to prototype, and easily make your game networked… or were they?

In reality, using RSET proved to be almost always a bad idea:

  • First, you have very little control over who is setting it beside the master keyword. Anything more complex than that, and you will need a function so you can base your logic on the RPC caller.
  • Second, performances are bad!

Take this example from the multiplayer bomber demo:

extends KinematicBody2D

puppet var puppet_pos = Vector2()
puppet var puppet_motion = Vector2()

func _physics_process(_delta):
    # ...
    if is_network_master():
        rset("puppet_motion", motion)
        rset("puppet_pos", position)
    else:
        position = puppet_pos
        motion = puppet_motion

    move_and_slide(motion * MOTION_SPEED)
    if not is_network_master():
        puppet_pos = position # To avoid jitter

It's making 2 separate rset()s, generating 2 packets, which may arrive each at a different frame under certain conditions. Additionally, while one would think it could use a direct RSET for the position, it actually can't. This is because it needs to add some logic to avoid jittering anyway.

Compare it with this:

extends KinematicBody2D

var puppet_pos = Vector2()
var puppet_motion = Vector2()

puppet func _update_state(p_pos, p_motion):
    puppet_pos = p_pos
    puppet_motion = p_motion

func _physics_process(_delta):
    # ...
    if is_network_master():
        rpc("_update_state", puppet_pos, puppet_motion)
    else:
        position = puppet_pos
        motion = puppet_motion

    move_and_slide(motion * MOTION_SPEED)
    if not is_network_master():
        puppet_pos = position # To avoid jitter

In this example, we only make 1 rpc(), reducing the network usage and latency. More importantly, we make sure that in each frame, either the client will have the old state, or a fully updated one. This avoids having an inconsistent state where the position is up-to-date but the motion isn't (or vice versa).

This is just one example which shows a common pitfall in networking.

So, to avoid tricking developers into these common mistakes (which could be unexpected for newcomers), and given the rarity of rset() usage in general, we decided to remove rset(). We really think you won't miss it in the end, but let us know if you feel you had a strong use case for them. Like most decisions in Godot, this is not set in stone.

Future work

If you feel a bit disappointed from this progress update, keep in mind this was just the ground work for more important changes. We have a few surprises in the works!

There's the new GDScript syntax for RPCs to talk about, the newly exposed ordered transfer mode, channels, ENet and WebRTC improvements, and even some news about the long-awaited node replication! So stay tuned for more :)

Reference work