A high-level Python multiplayer library. Just works.
Project description
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nodelink-0.6.0.tar.gz.
File metadata
- Download URL: nodelink-0.6.0.tar.gz
- Upload date:
- Size: 21.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9383536af08b764fb7f051afaf2c478382c16497d9d7beb0687c789c528fc3d
|
|
| MD5 |
6d42b5338524676cc9f86b6283ebce93
|
|
| BLAKE2b-256 |
3069db237bbb9c040e689e626eee844c484a9d70fc99fdf619b61a58a0f66c20
|
File details
Details for the file nodelink-0.6.0-py3-none-any.whl.
File metadata
- Download URL: nodelink-0.6.0-py3-none-any.whl
- Upload date:
- Size: 22.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cc69eba6c0617a713f0a221bdbe9d0a9bbd29886f4cd6062b5847a9f88848e4c
|
|
| MD5 |
b544190506ae31cbd183a3b28d823fe9
|
|
| BLAKE2b-256 |
1a2936d598adac6fdcc0a488cfe653729d3d1898e2abc4fd17fa95a8ccb8373c
|