Check out our latest project ✨ OpenChapter.io: free ebooks the way its meant to be 📖

Topic Router - Subscribe/Publish System

An asset by fbcosentino
The page banner background of a mountain and forest
Topic Router - Subscribe/Publish System hero image

Quick Information

0 ratings
Topic Router - Subscribe/Publish System icon image
fbcosentino
Topic Router - Subscribe/Publish System

A WebSocket-based Topic Router (fully in GDScript), where clients connect to a server to subscribe to topics, and/or to publish messages to topics. When a client publishes a message, a copy is sent to all clients subscribing to that topic.(Similar concept to services like MQTT.)

Supported Engine Version
4.4
Version String
1.0.0
License Version
MIT
Support Level
community
Modified Date
1 month ago
Git URL
Issue URL

README

Topic Router for Godot

A WebSocket-based Topic Router for Godot 4.x, where clients connect to a server to subscribe to topics, and/or to publish messages to topics. When a client publishes a message, a copy is sent to all clients subscribing to that topic. Fully implemented in GDScript, and no external apps or services required.

(If you're familiar with MQTT, it's the same logic, and topics follow the same syntax.)

This library adds two new nodes: TopicRouterServer and TopicRouterClient. Just like generic TCP servers and clients, Topic Router servers and clients can be on the same machine talking via localhost or through internet, and the server can be on the same Godot application/instance as one (or more) clients, or be a dedicated server application.

(In the case of MQTT, servers are called "brokers". I'm not using this name to avoid misleading plugin users into believing this would be a MQTT-compatible broker. This addon is unrelated to MQTT. The nodes provided here are only garanteed to be compatible to each other.)

This library is not suitable for real-time. You should use it for turn-based games, non-game apps, or if you're using it in real time games, you should use only for non-realtime tasks (like chat).

The internal protocol used for communication between the two nodes is made of JSON-stringified dictionaries, and very easy to modify if needed.


Subscribers and Publishing

A client can subscribe to a topic, which means this client wants to receive copies of any messages from that topic.

A client can publish a message to a topic, and when this happens, all clients who are subscribing to that topic receive a copy of the message.

You don't have to manually create any list of "allowed topics". They are added to an internal record automatically when a client subscribes to them.

Topic Formatting

Topics are of type String, and can have any content. There are only three characters with predefined meaning: /, + and #. If those don't show up in the string, the topic behaves as one literal identifier.

The standard practice is to use the / character to separate the topic in hierarchical levels (similar to directories in a path, or a URL). So as example you could use a topic lobby/join-leave to receive messages informing when a player has joined or left a lobby, and lobby/chat to receive messages sent to the lobby chat. But this is just a convention - there are no code limitations to what you decide your topics to be. A topic called "Hello world! I am a topic which looks like text!" would be perfectly valid.

Topics accept two wildcards when subscribing (not when publishing!). Because of wildcards, the string subscribed might not be identical to the topic it matched. For this reason, the terminology used in this plugin is: the client subscribes to a mask, and publishes to a topic.

  • + -> Single Level

When the character + appears as a level in the mask being subscribed, it means the client doesn't care what is in that level - in other words, that level should accept anything.

Example: if you are using a topic in the format enemy/<enemy-id>/<enemy-event> to inform clients about enemy events, a client can subscribe to a mask enemy/+/spawn to receive messages about enemies spawning, from any enemies. So a message published to a topic enemy/123/spawn will be received, as well as enemy/65421/spawn - or any other content in the second level. But a message to a topic enemy/123/death would not be received.

  • # -> All sublevels

When the character '#' appears at the end of a mask (alone as last level, e.g. lobby/settings/#), it means this mask will match any sublevel structure after it, not necessarily just one level. In other words, it means anything after this point doesn't matter, if what comes before the '#' matches, that's enough. That also includes the top level (that is, without the last /).

Example: a client subscribing to a mask enemy/123/# will receive messages published to enemy/123, as well as enemy/123/spawn, enemy/123/grid/positon, eneme/123/grid/time-added/seconds, and anything starting with enemy/123/.


TopicRouterServer

The TopicRouterServer node runs a WebSocket TCP server without any application-specific handshaking (that is, it doesn't try to confirm if the connecting client is a TopicRouterClient or not, it just assumes it is).

Place a TopicRouterServer node somewhere in the scene tree, and call the start() method from code. All default values for the exported properties should work. Default port is 9099. if you want to stop the server, call stop(). You should not touch any of the other methods.

The server also keeps an internal counter of message index, incremented sequentially on every new published message on any topic. This can be used to identify repeated messages (more on this later).

Signals

client_connected(peer_id: int, number_of_clients: int)

Emitted when a new client connects to the server. number_of_clients is the currently connected number of clients, including this one.

client_disconnected(peer_id: int, number_of_clients: int)

Emitted when a client disconnects from the server. number_of_clients is the remaining number of connected clients, not including this one anymore.

client_subscribed(peer_id: int, mask: String)

Emitted when a client subscribes to a mask. You don't have to do anything manually, all topic and routing is done internally. This is for notification only.

client_unsubscribed(peer_id: int, mask: String)

Emitted when a client unsubscribes from a mask. You don't have to do anything manually, all topic and routing is done internally. This is for notification only.


TopicRouterClient

The TopicRouterClient node connects to a TopicRouterServer, and is used to subscribe, unsubscribe, and publish messages. Again, there is no handshaking (it just assumes the server is a TopicRouterServer).

Place the TopicRouterClient node somewhere in the scene tree, set the server_url to the server address in WebSockets format (e.g. ws://127.0.0.1:9099), and use the methods and signals below.

Methods

connect_to_server()

Connects to a TopicRouterServer at the address specified in server_url. Should be called before any other operation (check for successful completion).

disconnect_from_server(code: int = 1000, reason: String = "")

Disconnects from the server. code and reason are the arguments supplied to WebSocketPeer.close().

subscribe(mask: String)

Subscribes to a topic mask. If already subscribed, the server will just ignore this request.

unsubscribe(mask: String)

Unsubscribes from a topic mask. If not currently subscribed, the server will just ignore this request.

publish(topic: String, message: String)

Publishes a message to a topic. The message string can be anything, as the content is not touched and just forwarded as is. (It's common practice to be a stringified JSON.)

If the topic doesn't exist (that is, no client is subscribed to any masks matching this topic), the server just ignores the request.

Signals

connected_to_server

Emitted when this client is connected to the server, and ready to subscribe and publish to topics.

disconnected_from_server

Emitted when this client is no longer connected to the server (or connection attempt failed), and should not attempt to subscribe or publish messages.

mask_subscribed(mask: String)

Emitted when successfully subscribed to a topic (received confirmation from the server).

mask_unsubscribed(mask: String)

Emitted when successfully unsubscribed from a topic (received confirmation from the server).

message_received(message_index: int, mask: String, topic: String, message: String)

Emitted when a message is received due to a mask this client subscribed to. A topic from a message can match more than one mask (in which case it will receive the message more than once), so mask contains the mask which caused this message to be received, while topic contains the actual topic the message was published to.

Example: if this client subscribes to two masks: lobby/123/# (e.g. any lobby notification about player 123) and lobby/+/join (e.g. lobby notification for anyone joining), then when a message is published to a topic lobby/123/join, this client will receive two separate messages - and therefore this signal will be emitted twice:

message_received(1, "lobby/123/#", "lobby/123/join", <message contents>)

and

message_received(1, "lobby/+/join", "lobby/123/join", <message contents>)

The message_index is a sequential number incremented on every new message published. The meaning of its value in itself is of no use to the client, but it can be used to identify the repeated messages (or if a message is older). Enabling the accept_only_new_messages flag causes the client to only accept the first message having that same index, ignoring further messages with same index (or lower). In that case, the situation above will not happen and the signal will be fired only for the first message. This also means your application will not be aware of the fact the topic also matched the second mask, so if this information is relevant, do not enable the flag. (You can still manually filter out the repeated message by reading the message_index field in your own code.)

A WebSocket-based Topic Router (fully in GDScript), where clients connect to a server to subscribe to topics, and/or to publish messages to topics. When a client publishes a message, a copy is sent to all clients subscribing to that topic.

(Similar concept to services like MQTT.)

Reviews

0 ratings

Your Rating

Headline must be at least 3 characters but not more than 50
Review must be at least 5 characters but not more than 500
Please sign in to add a review

Quick Information

0 ratings
Topic Router - Subscribe/Publish System icon image
fbcosentino
Topic Router - Subscribe/Publish System

A WebSocket-based Topic Router (fully in GDScript), where clients connect to a server to subscribe to topics, and/or to publish messages to topics. When a client publishes a message, a copy is sent to all clients subscribing to that topic.(Similar concept to services like MQTT.)

Supported Engine Version
4.4
Version String
1.0.0
License Version
MIT
Support Level
community
Modified Date
1 month ago
Git URL
Issue URL

Open Source

Released under the AGPLv3 license

Plug and Play

Browse assets directly from Godot

Community Driven

Created by developers for developers