nodelink
A high-level Python multiplayer library. Just works.
No presets. No opinions. You define the events, the data, and the behavior — nodelink handles the connections, state sync, and networking underneath.
pip install nodelink
Quickstart
Server:
from nodelink import Server
server = Server(port=5000)
@server.on("connect")
def on_connect(player):
server.broadcast("joined", {"id": player.id})
@server.on("move")
def on_move(player, data):
player.update_state({"x": data["x"], "y": data["y"]})
@server.on("disconnect")
def on_disconnect(player):
server.broadcast("left", {"id": player.id})
@server.on_error()
def on_error(error, player):
print(f"Error from {player.id}: {error}")
server.start()
Client:
from nodelink import Client
client = Client("localhost", 5000)
@client.on("connect")
def on_connect():
client.send("move", {"x": 10, "y": 20})
@client.on("joined")
def on_joined(data):
print(f"Player {data['id']} joined!")
@client.on("__state_sync__")
def on_sync(data):
for player_id, state in data["players"].items():
print(f"{player_id}: {state}")
@client.on_error()
def on_error(error):
print(f"Error: {error}")
client.connect()
Core concepts
Events
Everything is event-based. Send an event from the client, handle it on the server (and vice versa) with a simple decorator.
# Client sends:
client.send("chat", {"msg": "hello!"})
# Server handles:
@server.on("chat")
def on_chat(player, data):
server.broadcast("chat", {"from": player.id, "msg": data["msg"]})
# All clients receive:
@client.on("chat")
def on_chat(data):
print(f"[{data['from']}]: {data['msg']}")
Player state sync
Each player has a .state dict. Update it on the server and nodelink automatically broadcasts only the changed fields to all clients at 20 ticks/sec.
@server.on("move")
def on_move(player, data):
player.update_state({"x": data["x"], "y": data["y"]})
# That's it — state is synced automatically
Force an immediate sync if you can't wait for the next tick:
server.sync_state() # delta only
server.sync_state(full=True) # full state
Sending to one player
@server.on("connect")
def on_connect(player):
# Send something only to this player
asyncio.ensure_future(player.send("welcome", {"id": player.id}))
Error handling
Errors in event handlers are caught and routed to your error handler instead of crashing everything.
@server.on_error()
def on_error(error, player):
print(f"Error from {player.id}: {error}")
@client.on_error()
def on_error(error):
print(f"Client error: {error}")
Send queue
Messages sent before the client is fully connected are automatically queued and delivered the moment the connection opens. No timing issues.
client.send("hello", {"msg": "this works even before connect() is called"})
client.connect() # queued message is flushed on connect
API Reference
Server(host, port, tick_rate, delta_only)
| Arg | Default | Description |
|---|---|---|
host |
"0.0.0.0" |
Hostname to bind to |
port |
5000 |
Port to listen on |
tick_rate |
20 |
State syncs per second |
delta_only |
True |
Only broadcast changed fields |
| Method | Description |
|---|---|
server.on(event) |
Register an event handler |
server.on_error() |
Register an error handler |
server.broadcast(event, data) |
Send event to all players (thread-safe) |
server.kick(player_id) |
Disconnect a player by ID (thread-safe) |
server.sync_state(full=False) |
Force immediate state sync (thread-safe) |
server.get_players() |
List of all connected Players |
server.get_player(id) |
Look up a player by ID |
server.player_count |
Number of connected players |
server.start() |
Start the server (blocking) |
Client(host, port)
| Method | Description |
|---|---|
client.on(event) |
Register an event handler |
client.on_error() |
Register an error handler |
client.send(event, data) |
Send event to server |
client.connect() |
Connect and start listening (blocking) |
Player
| Attribute/Method | Description |
|---|---|
player.id |
Unique player ID |
player.state |
Dict of synced values |
player.set_state(key, value) |
Set one state value |
player.get_state(key, default) |
Get one state value |
player.update_state(dict) |
Merge dict into state |
player.clear_state() |
Wipe state entirely |
player.send(event, data) |
Send directly to this player |
player.connected_at |
Unix timestamp of connection time |
player.last_seen |
Unix timestamp of last message |
Built-in events
| Event | Where | Handler signature |
|---|---|---|
"connect" |
server | fn(player) |
"disconnect" |
server | fn(player) |
"connect" |
client | fn() |
"disconnect" |
client | fn() |
"__state_sync__" |
client | fn(data) — data["players"] is a dict of player_id -> state_delta; data["count"] is the current online player count |
Requirements
- Python 3.8+
websockets >= 12.0
License
MIT
Metadata
Release files for nodelink 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| nodelink-0.6.0.tar.gz | 21.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| nodelink-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 43.6 kB
Release files / nodelink-0.6.0.tar.gz
| Download URL | nodelink-0.6.0.tar.gz |
|---|---|
| Size | 21.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e9383536af08b764fb7f051afaf2c478382c16497d9d7beb0687c789c528fc3d
|
|
BLAKE2b-256 checksum How to use checksums |
3069db237bbb9c040e689e626eee844c484a9d70fc99fdf619b61a58a0f66c20
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.12
|
Release files / nodelink-0.6.0-py3-none-any.whl
| Download URL | nodelink-0.6.0-py3-none-any.whl |
|---|---|
| Size | 22.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cc69eba6c0617a713f0a221bdbe9d0a9bbd29886f4cd6062b5847a9f88848e4c
|
|
BLAKE2b-256 checksum How to use checksums |
1a2936d598adac6fdcc0a488cfe653729d3d1898e2abc4fd17fa95a8ccb8373c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.12
|