gale.net package

gale.net: a pure-Python, pygame-free toolkit to build multiplayer games over UDP, for LAN or internet play, with a hand-rolled reliability layer (no dependency on a third-party networking library), LAN discovery, configurable-format room codes (encode/decode) for sharing a host/port pair as a short, human-typeable string, and optional building blocks for client-side prediction/server reconciliation (PredictionBuffer) and entity interpolation/lag compensation (SnapshotInterpolator, lag_compensated_position).

See docs/examples/net.rst for a walkthrough.

Author: Alejandro Mujica (aledrums@gmail.com)

class gale.net.Channel[source]

Bases: object

Identifies how a message should be delivered.

  • UNRELIABLE: sent once, may be lost or arrive out of order. Use it for data that is superseded by the next update anyway, such as a position snapshot sent every frame.

  • RELIABLE_ORDERED: guaranteed to arrive, and to be delivered to on_message callbacks in the same order it was sent. Use it for events where both matter, such as a chat log.

  • RELIABLE_UNORDERED: guaranteed to arrive, but may be delivered out of order. Cheaper than RELIABLE_ORDERED since it never has to hold a packet back waiting for an earlier one. Use it for events that must not be lost but are independent of each other, such as “a point was scored”.

UNRELIABLE: int = 0
RELIABLE_ORDERED: int = 1
RELIABLE_UNORDERED: int = 2
class gale.net.Client(serialize=<function json_serialize>, deserialize=<function json_deserialize>, timeout=5.0)[source]

Bases: object

The connecting side of a gale.net game: connects to a single Server over a non-blocking UDP socket, and lets the game send messages to it and react to the ones it sends back.

Usage example:

client = Client() client.on_connect(lambda: print(“connected”)) client.on_message(“snapshot”, lambda payload: …) client.connect(“127.0.0.1”, 9000)

# In the game loop: client.update(dt) client.send(“input”, {“move”: “left”})

Parameters:
  • serialize (Callable[[str, Dict[str, Any]], bytes])

  • deserialize (Callable[[bytes], Tuple[str, Dict[str, Any]]])

  • timeout (float)

on_connect(callback)[source]
Parameters:

callback (Callable[[], None]) – Called with no arguments once the connection is established.

Return type:

None

on_connect_failed(callback)[source]
Parameters:

callback (Callable[[str], None]) – Called with a reason string if connect() times out without a reply.

Return type:

None

on_disconnect(callback)[source]
Parameters:

callback (Callable[[str], None]) – Called with a reason string when a previously established connection ends, explicitly or by timing out.

Return type:

None

on_message(message_type, callback)[source]
Parameters:
  • message_type (str) – The message type to react to.

  • callback (Callable[[Dict[str, Any]], None]) – Called with the payload every time a message of this type is received.

Return type:

None

connect(host, port, timeout=5.0)[source]

Start connecting to a Server. Non-blocking: the outcome is reported later, through update(), via the on_connect/ on_connect_failed callbacks.

Parameters:
  • host (str) – The server’s address.

  • port (int) – The server’s port.

  • timeout (float) – How long to keep retrying before giving up, in seconds. The default value is 5.0.

Return type:

None

get_rtt()[source]
Returns:

The smoothed round-trip time to the server, in seconds, or None if not connected.

Return type:

float | None

send(message_type, payload, channel=0)[source]
Parameters:
  • message_type (str) – The message type.

  • payload (Dict[str, Any]) – The message data.

  • channel (int) – One of the Channel constants. The default value is Channel.UNRELIABLE.

Return type:

None

disconnect()[source]

End the current connection, if any, telling the server so it does not have to wait for a timeout to notice. Does not notify this client’s own on_disconnect (that is reserved for connections that end unexpectedly).

Return type:

None

close()[source]

Close the underlying socket. The client is no longer usable afterwards.

Return type:

None

update(dt)[source]

Poll the network and drive the connection state: attempt/retry connecting, receive and dispatch messages, retransmit unacked reliable packets, send heartbeats, and detect a timed-out server.

Parameters:

dt (float) – Time elapsed, in seconds, since the last call. Currently unused (timing is wall-clock based), accepted for consistency with the rest of gale.

Return type:

None

class gale.net.Peer(peer_id, address, token)[source]

Bases: object

Represents one connected client, from the Server’s point of view.

Usage example:

for peer in server.get_peers():

print(peer.peer_id, peer.address, peer.rtt)

Parameters:
  • peer_id (int)

  • address (Tuple[str, int])

  • token (int)

update_rtt(sample)[source]

Fold a new round-trip-time sample into the smoothed estimate using an exponential moving average.

Parameters:

sample (float) – The latest measured round-trip time, in seconds.

Return type:

None

class gale.net.PredictionBuffer[source]

Bases: object

Buffers a client’s own predicted inputs/states so they can be replayed on top of an authoritative state sent back by the server (client-side prediction + server reconciliation).

Usage example:

buffer = PredictionBuffer()

# Every local frame, after applying input payload for dt # seconds and predicting the resulting state: sequence += 1 predicted_state = apply_input(current_state, payload, dt) buffer.record(sequence, payload, dt, predicted_state) client.send(“input”, {“sequence”: sequence, **payload})

# When the server reports how far it got: def on_snapshot(payload):

state = buffer.reconcile(

payload[“last_processed_sequence”], payload[“state”], apply_input,

) # state is where the client should render from now; # smoothly correcting towards it (rather than snapping) # to hide any discrepancy with what was predicted before # is left to the game, the same way gale.net leaves the # amount of snapshot-interpolation delay to the game (see # gale.net.interpolation).

apply_input has the signature (state, input_payload, dt) -> new_state, and must be a pure function of its arguments (no reliance on wall-clock time, randomness, or outside state) since it is replayed after the fact, possibly several times per reconcile() call.

property pending_count: int

The number of recorded inputs not yet discarded by reconcile(), i.e. still awaiting server acknowledgement.

Type:

returns

record(sequence, input_payload, predicted_state, dt=0.0)[source]

Store an input and the state predicted from applying it, keyed by sequence number, to be replayed later if the server’s authoritative state does not already account for it.

Parameters:
  • sequence (int) – This input’s sequence number. Must increase from one call to the next (matching the sequence the game sends to the server alongside the input).

  • input_payload (Dict[str, Any]) – The input applied this frame.

  • predicted_state (Dict[str, Any]) – The state that resulted from applying input_payload locally. Not used by reconcile() itself (it always replays from the authoritative state), but useful for the game to inspect/compare.

  • dt (float) – Time elapsed, in seconds, over which input_payload was applied. Replayed unchanged by reconcile(). The default value is 0.0.

Return type:

None

reconcile(last_processed_sequence, authoritative_state, apply_input)[source]

Discard every recorded input the server has already accounted for (sequence <= last_processed_sequence), then replay every remaining, unacknowledged input on top of authoritative_state, in the order it was recorded.

Parameters:
  • last_processed_sequence (int) – The highest input sequence number the server had already processed when it produced authoritative_state.

  • authoritative_state (Dict[str, Any]) – The state the server computed after processing inputs up through last_processed_sequence.

  • apply_input (Callable[[Dict[str, Any], Dict[str, Any], float], Dict[str, Any]]) – Function (state, input_payload, dt) -> new_state, applied once per remaining buffered input, in order.

Returns:

The reconciled state: authoritative_state with every unacknowledged input replayed on top of it.

Return type:

Dict[str, Any]

exception gale.net.RoomCodeError[source]

Bases: ValueError

Raised when a string isn’t a valid room code for a given format.

class gale.net.RoomCodeFormat(alphabet='0123456789ABCDEFGHJKMNPQRSTVWXYZ', group_size=5, group_separator='-')[source]

Bases: object

How a room code is rendered: which characters it can use, how many of them are grouped together, and what separates the groups. Two RoomCodeFormat instances with the same alphabet and group_size round-trip each other’s codes regardless of group_separator, since decoding strips every group_separator character before decoding.

Usage example:

# “7qk3-m2x-hdr” style: lowercase, 4-3-3 groups, dash-separated. my_format = RoomCodeFormat(

alphabet=”0123456789abcdefghjkmnpqrstvwxyz”, group_size=4,

)

Parameters:
  • alphabet (str)

  • group_size (int)

  • group_separator (str)

alphabet: str = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'
group_size: int = 5
group_separator: str = '-'
property char_count: int

How many characters, in this format’s alphabet, are needed to represent any (host, port) pair.

Type:

returns

class gale.net.Server(port, host='0.0.0.0', max_peers=None, serialize=<function json_serialize>, deserialize=<function json_deserialize>, heartbeat_interval=1.0, timeout=5.0)[source]

Bases: object

The authoritative/listening side of a gale.net game: accepts connections from any number of Clients over a single non-blocking UDP socket, and lets the game send/broadcast messages to them and react to the ones they send back.

Usage example:

server = Server(port=9000) server.on_connect(lambda peer: print(f”{peer.peer_id} joined”)) server.on_message(“input”, lambda peer, payload: …)

# In the game loop: server.update(dt) server.broadcast(“snapshot”, {“positions”: […]})

Parameters:
  • port (int)

  • host (str)

  • max_peers (int | None)

  • serialize (Callable[[str, Dict[str, Any]], bytes])

  • deserialize (Callable[[bytes], Tuple[str, Dict[str, Any]]])

  • heartbeat_interval (float)

  • timeout (float)

on_connect(callback)[source]
Parameters:

callback (Callable[[Peer], None]) – Called with the new Peer every time a Client connects.

Return type:

None

on_disconnect(callback)[source]
Parameters:

callback (Callable[[Peer], None]) – Called with the Peer every time a connected Client disconnects (explicitly or by timing out).

Return type:

None

on_message(message_type, callback)[source]
Parameters:
  • message_type (str) – The message type to react to.

  • callback (Callable[[Peer, Dict[str, Any]], None]) – Called with (peer, payload) every time a message of this type is received from any peer.

Return type:

None

enable_lan_discovery(name, discovery_port=9998)[source]

Start answering LAN discovery requests (see gale.net.discovery) so a Client can find this Server without knowing its address ahead of time. Only meant for servers reachable on a local network: do not call this on a public, internet-facing dedicated server.

Parameters:
  • name (str) – The name shown to clients discovering this server.

  • discovery_port (int) – The UDP port to listen for discovery requests on. The default value is DEFAULT_DISCOVERY_PORT.

Return type:

None

get_peers()[source]
Returns:

The list of currently connected peers.

Return type:

List[Peer]

get_rtt(peer_id)[source]
Parameters:

peer_id (int) – The peer to query.

Returns:

The peer’s smoothed round-trip time in seconds, or None if peer_id is not connected.

Return type:

float | None

send_to(peer_id, message_type, payload, channel=0)[source]
Parameters:
  • peer_id (int) – The peer to send to.

  • message_type (str) – The message type.

  • payload (Dict[str, Any]) – The message data.

  • channel (int) – One of the Channel constants. The default value is Channel.UNRELIABLE.

Return type:

None

broadcast(message_type, payload, channel=0, exclude=None)[source]
Parameters:
  • message_type (str) – The message type.

  • payload (Dict[str, Any]) – The message data.

  • channel (int) – One of the Channel constants. The default value is Channel.UNRELIABLE.

  • exclude (Iterable[int] | None) – Peer ids to skip. The default value is None.

Return type:

None

disconnect(peer_id)[source]
Parameters:

peer_id (int) – The peer to disconnect.

Return type:

None

close()[source]

Close the underlying sockets. The server is no longer usable afterwards.

Return type:

None

update(dt)[source]

Poll the network and drive every peer’s connection state: receive and dispatch messages, retransmit unacked reliable packets, send heartbeats, and detect timed-out peers.

Parameters:

dt (float) – Time elapsed, in seconds, since the last call. Currently unused (timing is wall-clock based), accepted for consistency with the rest of gale.

Return type:

None

class gale.net.ServerInfo(name, address, player_count)[source]

Bases: NamedTuple

Information about a Server found through LAN discovery.

Parameters:
  • name (str)

  • address (Tuple[str, int])

  • player_count (int)

name: str

Alias for field number 0

address: Tuple[str, int]

Alias for field number 1

player_count: int

Alias for field number 2

class gale.net.SnapshotInterpolator[source]

Bases: object

Buffers timestamped snapshots of a single piece of remote state and reconstructs it at an arbitrary past moment by linearly interpolating between the two snapshots that bracket it.

Usage example:

history = SnapshotInterpolator()

client.on_message(

“snapshot”, lambda payload: history.add(payload)

)

# Every render frame, rendering slightly in the past so there # is usually a snapshot on either side to interpolate between: render_time = time.monotonic() - interpolation_delay state = history.sample(render_time)

if state is not None:

draw_car(state[“x”], state[“y”], state[“heading”])

One SnapshotInterpolator holds the history for one remote entity; a game with several remote entities (other cars, other players) keeps one instance per entity.

add(data)[source]
Parameters:

data (Dict[str, Any]) – A JSON-serializable dict of numeric (possibly nested) fields, stamped with the local arrival time.

Return type:

None

sample(render_time)[source]
Parameters:

render_time (float) – The moment (in time.monotonic() terms) to reconstruct the state for.

Returns:

The interpolated data, the oldest snapshot if render_time is at or before it, the newest snapshot if render_time is at or after it, or None if nothing has been added yet.

Return type:

Dict[str, Any] | None

gale.net.decode(code, fmt=RoomCodeFormat(alphabet='0123456789ABCDEFGHJKMNPQRSTVWXYZ', group_size=5, group_separator='-'))[source]
Parameters:
  • code (str) – A code produced by encode() with the same fmt (whitespace, group_separator, and, for a same-case alphabet, letter case are all ignored).

  • fmt (RoomCodeFormat) – The format code was rendered in. The default value is DEFAULT_FORMAT.

Returns:

The (host, port) pair encode() was given.

Raises:

RoomCodeError – If code contains a character outside fmt.alphabet.

Return type:

Tuple[str, int]

gale.net.discover_lan_servers(discovery_port=9998, timeout=1.0)[source]

Broadcast a discovery request on the local network and collect every reply that comes back within timeout seconds.

Parameters:
  • discovery_port (int) – The UDP port Servers are listening for discovery requests on (see Server.enable_lan_discovery). The default value is DEFAULT_DISCOVERY_PORT.

  • timeout (float) – How long to wait for replies, in seconds. The default value is 1.0.

Returns:

The list of ServerInfo found, one per replying Server.

Return type:

List[ServerInfo]

gale.net.encode(host, port, fmt=RoomCodeFormat(alphabet='0123456789ABCDEFGHJKMNPQRSTVWXYZ', group_size=5, group_separator='-'))[source]
Parameters:
  • host (str) – An IPv4 address (e.g. “203.0.113.7”) or a resolvable hostname.

  • port (int) – A UDP/TCP port, between 0 and 65535.

  • fmt (RoomCodeFormat) – The format to render the code in. The default value is DEFAULT_FORMAT (“XXXXX-XXXXX”, Crockford base32).

Returns:

A short code that decode() can turn back into (host, port).

Return type:

str

gale.net.json_deserialize(data)[source]
Parameters:

data (bytes) – Bytes produced by json_serialize (or a wire-compatible encoder).

Returns:

A tuple (message_type, payload).

Raises:

ValueError – If data is not valid JSON or does not have the expected shape.

Return type:

Tuple[str, Dict[str, Any]]

gale.net.json_serialize(message_type, payload)[source]
Parameters:
  • message_type (str) – The message type/name.

  • payload (Dict[str, Any]) – The message’s data.

Returns:

The UTF-8 encoded JSON representation of {“type”: message_type, “payload”: payload}.

Return type:

bytes

gale.net.lag_compensated_position(history, rewind_time, field_path=None, now=None)[source]

Rewind a target’s snapshot history back to how a (laggy) shooter perceived it, for server-side hit detection: instead of testing a shot against the target’s current, authoritative position, test it against where the target was rewind_time seconds ago, which is approximately where the shooter actually saw it when they fired.

rewind_time is typically the shooter’s RTT / 2 (one-way trip from server to shooter) plus whatever interpolation delay the shooter’s own SnapshotInterpolator/rendering uses.

Parameters:
  • history (SnapshotInterpolator) – The target’s snapshot history.

  • rewind_time (float) – How far back in time, in seconds, to rewind.

  • field_path (Sequence[str] | None) – Optional sequence of keys to drill into the sampled snapshot (e.g. (“position”,) to get just the nested position dict). The default value is None, meaning the whole sampled snapshot is returned.

  • now (float | None) – The current time in time.monotonic() terms. The default value is None, meaning time.monotonic() is used.

Returns:

The target’s interpolated state at the rewound moment (or the value at field_path within it), or None if history has no snapshots yet.

Return type:

Any | None

Submodules