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:
objectIdentifies 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:
objectThe 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:
objectRepresents 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)
- class gale.net.PredictionBuffer[source]
Bases:
objectBuffers 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:
ValueErrorRaised 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:
objectHow 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:
objectThe 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_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
- 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:
NamedTupleInformation 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:
objectBuffers 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