Skip to main content

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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nodelink-0.6.0.tar.gz (21.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nodelink-0.6.0-py3-none-any.whl (22.4 kB view details)

Uploaded Python 3

Release history Release notifications | RSS feed

This release

0.6.0 This release

2 files

0.5.0

2 files

0.4.1

2 files

0.4.0

2 files

0.3.2

2 files

0.3.1

2 files

0.3.0

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page