Skip to content

05 — Receiving Packets

← Sending Packets | Table Of Contents | Next: Connection Lifecycle →


on\<T>() — Registering A Push Callback

Receives a push packet the server sends, via a callback.

struct leaderboard_update_t {
    int32_t rank;
    std::string player_id;
    int64_t score;
};

connector.on<leaderboard_update_t>([](const leaderboard_update_t& update) {
    // update.rank, update.player_id, update.score
});

The packet name can be specified explicitly.

connector.on<leaderboard_update_t>(
    "leaderboard.weekly",
    [](const leaderboard_update_t& update) {
        // process only the weekly leaderboard
    });

on() adds to a callback list owned by the connector. When the connector closes, the callbacks are also removed. Currently, there's no API to individually unregister an already-registered callback.

Dispatch Mode

dispatch_mode decides when an on<T>() callback runs.

Manual Mode (Default)

The callback runs on the thread that called dispatch(). Use this to align with a game engine's frame loop.

// connector_options_t::dispatch_mode = dispatch_mode_t::manual (default)

// game loop
while (running) {
    connector.dispatch(); // runs pending push-packet callbacks
    update_game_state();
    render();
}

dispatch() processes whatever callbacks are pending at call time and returns. It doesn't wait for a newly arriving packet.

Immediate Mode

The callback runs immediately on the connector's receive path. No call to dispatch() is needed. Suits a CLI, tool, or e2e client.

options.dispatch_mode = zlink::stream_connector::dispatch_mode_t::immediate;

Even in immediate mode, the callback runs outside the connector's internal lock. It's safe to call connector.send() inside the callback.

wait_for\<T>() — Waiting For A Specific Packet

Waits once for a specific packet, with no callback registered. A matched packet is consumed, and is not delivered to an on<T>() callback afterward.

struct server_ready_t {
    std::string server_id;
    int32_t player_capacity;
};

auto ready = connector
    .wait_for<server_ready_t>()
    .packet_name("server.ready")
    .timeout(std::chrono::seconds{10})
    .submit();

if (!ready) {
    // ready.error_code() == error_code_t::request_timeout, etc.
    return;
}

auto capacity = ready.value().player_capacity;

If timeout is omitted, connector_options_t::wait_timeout is used.

where() — Conditional Filter

Consumes only a packet matching a specific condition. A packet that doesn't match isn't consumed — it stays in the queue, to be processed by a later wait or dispatch.

auto my_match = connector
    .wait_for<match_found_t>()
    .where([](const match_found_t& msg) {
        return msg.match_id == "match-7f3a";
    })
    .timeout(std::chrono::seconds{30})
    .submit();

If you only need to check whether a single field equals a specific value, you can use the member-pointer overload. This avoids repeating a C++ lambda's parameter declaration.

auto my_match = connector
    .wait_for<match_found_t>()
    .where(&match_found_t::match_id, std::string("match-7f3a"))
    .timeout(std::chrono::seconds{30})
    .submit();

The wait_for Callback Style

connector
    .wait_for<server_ready_t>()
    .packet_name("server.ready")
    .submit([](zlink::stream_connector::result_t<server_ready_t> result) {
        if (!result) { return; }
        // result.value()
    });

Connection State Events

connector.on_connection_state_changed([](const zlink::stream_connector::connection_state_changed_t& ev) {
    // ev.state: created, connecting, connected, reconnecting, disconnected, closed
});

connector.on_disconnected([]() {
    // the connection dropped (before a reconnect attempt)
});

connector.on_error([](const zlink::stream_connector::error_t& err) {
    // err.code, err.message
});

Calling send/request Inside A Callback

You can call the same connector's send(), request() while a callback is running. The implementation doesn't run the user callback while holding the connector's internal lock.

connector.on<pvp_invite_t>([&connector](const pvp_invite_t& invite) {
    // safe to send inside the callback
    connector
        .send(pvp_accept_t{invite.match_id, "player-1"})
        .packet_name("pvp.accept")
        .submit();
});

pending_dispatch_count()

Checks the number of pending callbacks in manual mode.

auto pending = connector.pending_dispatch_count();