This is a "cheat sheet" for "cmnRPC" extension by YellowAfterlife.
A version of this page that covers old GameMaker versions can be found here.
Click on sections to expand/collapse them.
Quick display controls: Categories · Sections · Everything ·

The premise

Following classic RPC principles, the best way to think about cmnRPC is as a networked version of script_execute - you tell remote player(s) to run a specific script/function, and they will do so as soon as they receive the request over network.

The rest is up to you - for example, if you have a cooperative shooter game of some kind, you can have the host/lobby owner order everyone to create an enemy and assign it a unique ID (e.g. just a number that counts up each time you do this) and base the rest of manipulations with it ("I've hit this enemy" / "this enemy is now moving over here") around the said ID.

Getting started

Initial setup:

  • Import cmnRPC extension into your project.
  • Add the following to a persistent controller object's Step event:

    rpc_net_update();
    
  • Add the following to a persistent controller object's Async - Network event:

    rpc_adapter_tcp_async();
    

Additional setup for Steam API:

  • Import cmnRPC_Steam and Steamworks extensions
  • Add the following to a persistent controller object's Async - Steam event:

    rpc_adapter_steam_async();
    

The basics:

Network

The following relate to networking core in cmnRPC.

Functions
rpc_net_reset()

Fully resets the extension state.

This is done automatically in host/join.

rpc_net_host(port, ?then, ?custom)

Attempts to host a server on the given port.

Custom adapters may interpret the port differently.

Calls rpc_net_on_host once the result is known.

rpc_net_join(url, port, ?then, ?custom)

Attempts to join a server at the given endpoint.

Custom adapters may interpret the url+port differently.

Calls rpc_net_on_join once the result is known.

rpc_net_update()

Should be ran in step event of some controller object.

Configuration
rpc_net_adapter:rpc_adapter
rpc_net_version:string

The game version. The server will reject connections with a mismatched game version.

rpc_net_version = "1.4";
rpc_net_local_name:string
rpc_net_max_clients:int

Maximum number of connected clients when hosting.

rpc_net_max_clients = 3; // 4P total (including you)
rpc_net_set_allow_join(canJoin)

Sets whether the game can be joined.

It is common to "lock" the game if you don't allow mid-session joining.

Custom adapters may interpret this differently to show/hide lobby from lists.

rpc_net_allow_join:bool
rpc_log:function(message)

This is called whenever cmnRPC needs to log debug information.

By default this will call show_debug_message.

rpc_log_level:int

Determines what information cmnRPC will log via rpc_log.

Levels are as following:

  • 1: Info, warnings, errors
  • 2: Warnings, errors
  • 3: Errors only
  • 4: Nothing
Status

The following read-only variables let you easily fetch some information about cmnRPC state.

rpc_net_is_active:bool

Indicates that we are in a game (hosting or joined).

This is set to true as soon as calling host/join functions.

rpc_net_is_server:bool

Indicates whether we are the server.

This is set to true when not hosting/joining to simplify the logic.

rpc_net_is_connecting:bool

Indicates that we are a client and currently trying to connect to a server.

rpc_net_is_creating:bool

Indicates whether we are the server and currently trying to host a game.

rpc_net_local_uid:int

Your own UID.

rpc_net_server_uid:int

UID of the server-player.

If you are the server, this is equal to rpc_net_local_uid.

Callbacks

The following are global variables that you can assign scripts to and the extension will call them at according times.

Here, "status" is a rpc_status_* value.

rpc_net_on_host:function(status, custom)

Is called once it is known whether you've successfully hosted a server.

rpc_net_on_join:function(status, custom)

Is called once it is known whether you've successfully hosted a server.

rpc_net_on_disconnect:function(status)

Is called by clients when connection to server is lost. Reason is provided.

rpc_net_on_departure:function(uid, reason)

Is called by clients and server when someone leaves the game.
This happens immediately before the user information is freed up.

This will also be called when someone times out.

rpc_net_on_arrival:function(uid, name)

Is called when a client joins - both for server and other clients.

Calls
rpc_call(...args, script)

Calls the given script for everyone, including you.

For example,

rpc_call(keyboard_string, function(_text) {
    show_debug_message(rpc_user_get_name(rpc_sender) + ": " + _text);
});

// or:
if (keyboard_check_pressed(vk_enter)) {
    rpc_call(keyboard_string, scr_text_message);
}
// and in scr_text_message:
function scr_text_message(_text) {
    show_debug_message(rpc_user_get_name(rpc_sender) + ": " + _text);
}

would show "<player name>: <text>" for everyone.

rpc_call_ns(...args, script)

Calls the given script for everyone, except you.

For example,

x += xspeed;
y += yspeed;
rpc_call_ns(x, y, function(_x, _y) {
    with (obj_player) if (uid == rpc_sender) {
        x = _x;
        y = _y;
        break;
    }
});

// or:
x += xspeed;
y += yspeed;
rpc_call_ns(x, y, scr_sync_player);

// and in scr_sync_player:
function scr_sync_player(_x, _y) {
    with (obj_player) if (uid == rpc_sender) {
        x = _x;
        y = _y;
        break;
    }
}
rpc_call_for(uid, ...args, script)​bool

Calls the given script for the given player.

If the UID happens to be your own, this executes it for yourself without sending anything over network.

For example,

rpc_call_for(rpc_net_server_uid, block.x, block.y, function(_req_x, _req_y) {
    with (obj_block) if (x == _req_x && y == _req_y) {
        // (add server-side validation if needed)
        rpc_call(x, y, function(_break_x, _break_y) {
            with (obj_block) if (x == _break_x && y == _break_y) {
                instance_destroy();
                break;
            }
        });
        break;
    }
});

// or:
rpc_call_for(rpc_net_server_uid, block.x, block.y, scr_req_break_block);

// then in scr_req_break_block:
function scr_req_break_block(_x, _y) {
    with (obj_block) if (x == _x && y == _y) {
        // (add server-side validation if needed)
        rpc_call(x, y, scr_break_block);
        break;
    }
}

// and in scr_break_block:
function scr_break_block(_x, _y) {
    with (obj_block) if (x == _x && y == _y) {
        instance_destroy();
        break;
    }
{
rpc_sender:rpc_uid

Inside RPC scripts, this holds the UID of whomever that sent the message.

You can use this to perform any necessary validation.

Users

The following let you manage connections with remote players.

rpc_user_get_count()​int

Returns the number of other users in the session.

rpc_user_get_uid_at(pos)​uid

Returns the UID of the user at given position (0...count-1).

rpc_user_get_name(uid)​string

Returns the display name for the user with given UID, as set via rpc_net_local_name.

Returns "" if UID is invalid / user could not be found.

rpc_user_get_remote(uid)​any

May vary by platform, such as:

  • TCP: Socket ID
  • UDP: IP-Port pair
  • Steam: Steam user ID
  • etc.

Note: you don't know remotes of other players as client nor your own.

rpc_user_drop(uid)​bool

Attempts to kick the user with specified UID, returns whether successful.

The function will fail if:

  • You are not the server
  • The given user doesn't exist (or was already kicked)
  • You are trying to kick yourself
Status

Status codes:

rpc_status_ok

All is well!

rpc_status_not_implemented

May be returned by custom adapters, but not by default.

rpc_status_no_network

May be returned by custom adapters.

rpc_status_port_taken

Returned by default TCP/UDP adapters if server cannot be bound to port.

rpc_status_timeout

Connection timeout.

rpc_status_version_mismatch

You have tried to join a server but your version doesn't match.

rpc_status_leaving

The player is leaving voluntarily.

rpc_status_kicked
rpc_status_already_started

The session has already started and thus cannot be joined.

rpc_status_disconnected

Connection closed but reason is not known

Helpers:

rpc_status_get_name(status)​string

Returns a string name for the given RPC status code.

Advanced topics
Value type hinting

The following let you hint argument types when passing them to call functions to pack values slightly tighter.

This is a little less efficient than rpc_script_args (as it'll still use a prefix byte for most types), but is sufficient for most cases.

rpc_boolean(v)​rpc_wrap

A boolean. Fits into one byte as you'd expect.

rpc_s4(v)​rpc_wrap

An integer in -8..+7 range.
This packs the argument into a single byte.

rpc_u8(v)​rpc_wrap

An integer in 0..255 range.

rpc_s12(v)​rpc_wrap

An integer in -2048..+2047 range.
This packs the argument into two bytes.

rpc_u16(v)​rpc_wrap

An integer in 0..65535 range.

rpc_s20(v)​rpc_wrap

An integer in -524280..+524279 range. Takes up 3 bytes.

rpc_s32(v)​rpc_wrap

An integer in -2147483648..+2147483647

rpc_u32(v)​rpc_wrap

An integer in 0..4294967295

rpc_s64(v)​rpc_wrap

A 64-bit integer

rpc_f32(v)​rpc_wrap

A 32-bit float

rpc_f64(v)​rpc_wrap

A 64-bit float

rpc_string(str)​rpc_wrap

Your normal null-terminated string

rpc_string_compressed(str)​rpc_wrap

[GMS2 only] Compresses a string using buffer_compress.
Good for large monotonous strings like JSON or INI-based save files.

rpc_map(map)​rpc_wrap

A single-layer ds_map.

Note that this creates a map on receiving end and you will want to destroy that.

rpc_list(list)​rpc_wrap

A single-layer ds_list.

Note that this creates a list on receiving end and you will want to destroy that.

rpc_bytes(buffer, offset, length)​rpc_wrap

Sends a part of the given buffer.

Note that this creates a buffer on receiving end and you will want to destroy that.

Custom value types
rpc_script_args:ds_map<script,array>

For cases where the bandwidth is of uttermost concern, you may define custom argument handlers on per-script basis.

Argument scripts will be called with 3 arguments - the buffer to write/read from, whether this is a write operation, and the value (if it is a write operation). The script should return the value on read operation and can return whatever on write.

For arguments out of specified argument range, the last defined argument's script will be called.

// on init:
rpc_script_args[?scr_sync_pos] = [scr_proc_s16, scr_proc_s16];
// as per above, this would also work here:
// rpc_script_args[?scr_sync_pos] = [scr_proc_s16];

// later:
rpc_call_ns(x, y, scr_sync_pos);

// in scr_proc_s16:
function scr_proc_s16(_buf, _write, _val) {
    if (_write) {
        buffer_write(_buf, buffer_s16, _val);
    } else return buffer_read(_buf, buffer_s16);
}
rpc_script_buffer_type:buffer_type

This defines how cmnRPC is going to write script indexes.

By default this is set to buffer_u16, but you may change it to buffer_u8 if your RPC-related scripts all are in ID<256 range, or increase it to buffer_u32 if you somehow have more than 65536 scripts.

Filtering

Scripts can be assigned into these two global variables to filter RPC packets. You may want to do so to automatically discard packets not belonging to the current game state.

rpc_filter_write:function(buffer, script)

Called after writing the script arguments, may write additional information into the buffer.

rpc_filter_write = scr_filter_write;

// in scr_filter_write:
buffer_write(argument0, buffer_s32, global.context);
rpc_filter_read:function(buffer, script)​bool

Called after reading the script arguments, may read what you wrote in the previous step and return whether to process the packet.

rpc_filter_write = scr_filter_read;

// in scr_filter_write:
return buffer_read(argument0, buffer_s32) == global.context;
Custom adapters

...

new rpc_adapter()

Creates a blank RPC adapter for you to fill out.

rpc_adapter_receive(from, buf, size)

Should be called by your adapter whenever a packet is received.

endpoint can be any value so long as you provide the same endpoint for the same sender (e.g. socket ID or )

rpc_adapter_client_connected(who)

Should be called when a client connects.

Can be ignored for connection-less protocols.

rpc_adapter_client_disconnected(who)

Should be called when a client disconnects.

Can be ignored for connection-less protocols.

rpc_adapter_server_created(status, then, custom)

Should be called when you find out whether a server could be created.

rpc_adapter_connected_to_server(status, who, then, custom)

Should be called when you find out whether you could connect to a server.

rpc_net_call_soon(fn, ?args)

Essentially a delayed script_execute - calls the given script with given arguments the next time rpc_net_update is called.

This can be used when you want something to happen the next frame, but not immediately.

Field macros:

kind:string

This should be set to something that uniquely identifies this adapter.

var q = new RpcAdapter();
q.kind = "Steam";
reset:function()

Called to cleanup the adapter state.

This is called by rpc_net_reset and should drop the existing connections (if any).

update:function()

Is called once per frame.

send:function(to, buf, size)​bool

Is called when something should be sent.

Should return whether it succeeded.

disconnect:function(remote)

Is called when needing to drop connection to remote.

For connection-less protocols this can be ignored.

host:function(port, then, custom)
join:function(url, port, then, custom)
set_allow_join:function(allow)
Built-in adapters
TCP adapter
UDP adapter

This adapter uses connection-less UDP.

Note that it does not automatically handle packet loss/integrity, so you would want to enable reliable UDP (via network_set_config) when not targeting platforms with custom UDP socket types.

new rpc_adapter_udp()
rpc_adapter_udp_socket_type:network_type

This is a value from network_socket_* enumeration that will determine the type of socket to create. By default this is network_socket_udp, but you may change it to platforms-specific socket types.

rpc_adapter_udp_async()

Should be called in Async - Network event.

Steam adapter