Tiny Lobby is designed for scalability and performance using a multi-threaded architecture and lockfree queues. It uses the following threads:
- Websocket Thread: Receives and Sends messages to clients. Connects 1-1 to: Game Thread through a queue, and to Authentication Thread through a queue.
- Authentication Thread: Does validation of tokens and loads data from database.
- Database Thread: Used by Game Thread to write data asynchronously.
- Game Thread: Receives messages, updates lobby state or runs scripts and sends response back to the Websocket Thread through a queue.
flowchart TD
subgraph Client
WebSocket_Client
end
subgraph Server
WebSocket_Thread
Game_Thread
Database_Thread
Authentication_Thread
end
WebSocket_Client -- Websocket Message --> WebSocket_Thread
WebSocket_Thread -- Authentication Token --> Authentication_Thread
Authentication_Thread -- Read Data --> Database_Thread
Authentication_Thread -- Validation --> WebSocket_Thread
WebSocket_Thread -- Receive Data --> Game_Thread
Game_Thread -- Write Data --> Database_Thread
Game_Thread -- Send Data --> WebSocket_Thread
- WebSocket Thread: Handles all client connections and message routing.
- Game Thread: Manages lobby state and logic.
- Database Thread: Handles database operations.
- Authentication Thread: Processes login and token validation.
Tiny Lobby provides a rich set of features, sent as messages that can be batched. Messages are typically sent as JSON objects with short keys for efficiency. Each message includes a command type, relevant data, and optional metadata.
- Write backend game logic in Lua that runs directly on the lobby server.
Peers can:
- Create a new lobby.
- Join a lobby and receive its state.
- Leave the lobby.
- Get lobby public data.
- Get lobby tags
- Get a list of lobbies.
- Receive notification when a peer joins.
- Receive notification when a peer leaves.
- Receive notification when a peer is kicked.
- Call lobby scripted functions.
The host of a lobby can:
- Change lobby sealed (locked/open) state.
- Change max players.
- Set or remove lobby password.
- Change lobby title.
- Set lobby tags.
Peers are able to:
- Receive initial state
- Set or unset ready state.
- Set or update user data.
- Send chat messages.
- Receive notification when a peer reconnects or disconnects.
- Receive notification when a peer sets user data.
- Receive notification when a peer public data is updated, or when own private data is updated.
- Receive chat messages.
The protocol is using websockets. When connecting to the server, you need to provide in protocols:
- as first protocol
appsinacup. - as second protocol the
game_id. - optionally as third protocol the
reconnection_token. If none is provided one will be generated for you.
From here on, after you connect, you will receive one initial response, afterwards you will be able to send commands and will receive responses to those.
A message is a json object. For the case of commands (client sent) it's a single json object, for the case of responses (server sent) it's either a single json or an array of messages (if batched).
The message for responses is of the following form:
{
"c": 1,
"d": {},
"m": "Message",
"e": true
}Where:
cis theCOMMAND: This is an integer corresponding to the response type id.dis theDATA: This is a dictionary corresponding to the data of the response.eis theIS_LOGICAL_ERROR: In the case of an error message, is this a user error or a server error.mis theMESSAGE: For server errors, this returns the error message.
The message for commands is of the following form:
{
"c": 1,
"d": {}
}Where:
cis theCOMMAND: This is an integer corresponding to the command type id.dis theDATA: This is a dictionary corresponding to the data of the command.
In both of the response and command, inside the data there is a special id field which is used for asynchronous messages (determining if the response is a response to a previous command). When issuing a command, if you put inside the data an id with a number, the same id will be received back when receiving a response.
Eg.:
- Command:
{
"c": 7,
"d": {
"id": 13
}
}- Response:
{
"c": 8,
"d": {
"id": 13
}
}The command numbers in the example above are fictive.
The lobby data will look like this:
{
"id": "abc", // lobby id
"n": "lobby_name", // lobby name
"h": "host_id", // host id
"s": true, // sealed state
"m": 4, // max peers
"p": ["host_id"], // peer ids
"_p": false, // has password
"c": 123456, // created time
"t": { "tag1": "value1" }, // tags
"d": { "data": "value" }, // public data
"_d": { "data": "value" }, // private data
}The peer data will look like this:
{
"id": "abc", // peer id
"oi": 1, // order id
"ud": { "user_data": "value" }, // user data
"r": false, // ready state
"l": "lobby_id", // lobby id
"d": { "public_data": "value" }, // public data
"dc": false, // disconnect state
"p": "anon", // platform
"rt": "abc123", // reconnection token
"_d": { "private_data": "value" } // private data
}Responses sent from the server.
This response is sent when an error occurs. It can be either a script error or a server error.
// This is an error thrown by the server
{
"c": 0,
"m": "Game not found"
}// This is an error thrown from the script by the developer
{
"c": 0,
"m": "The player cannot move to the left", // the message
"e": true // is logical error
}This response is sent when a lobby host is changed. The new host_id is sent.
{
"c": 1,
"d": {
"h": "host_id" // host id
}
}This response is sent when a lobby is created. The lobby and peers are sent.
{
"c": 2,
"d": {
"l": {
// Lobby Model
},
"p": [
{
// Peer Model
}
]
}
}This response is sent when a lobby is unsealed.
{
"c": 3,
"d": {}
}This response is sent when a lobby is sealed.
{
"c": 4,
"d": {}
}This is a response sent when a lobby is resized.
{
"c": 5,
"d": {
"m": 4 // max peers
}
}This is a response sent when a lobby password state changed.
{
"c": 6,
"d": {
"_p": true // if this lobby has a password
}
}This is a response sent when a lobby title changed.
{
"c": 7,
"d": {
"n": "title" // the lobby title
}
}This is a response sent when a peer is ready.
{
"c": 8,
"d": {
"p": "peer_id" // peer id
}
}This is a response sent when a peer is unready.
{
"c": 9,
"d": {
"p": "peer_id" // peer id
}
}This is a response sent when you leave a lobby.
{
"c": 10,
"d": {}
}This is a response sent when a peer leaves a lobby.
{
"c": 11,
"d": {
"p": "peer_id", // peer id
"k": true // if you were kicked form the lobby
}
}This is a response sent when you are kicked from a lobby.
{
"c": 12,
"d": {}
}This is a response sent when you connect to the server.
{
"c": 13,
"d": {
"p": {
// Peer Model
}
}
}This is a response sent when a lobby function that you called finishes executing.
{
"c": 14,
"d": {
"r": "result" // The function output
}
}This is a response sent when peer user data changes.
{
"c": 15,
"d": {
"ud": { "user_data": "value" }, // peer user data
"p": "peer_id" // peer id
}
}This is a response sent when lobby tags change.
{
"c": 16,
"d": {
"t": { "lobby_tags": "value" } // lobby tags
}
}This is a response sent when a peer sends a chat message.
{
"c": 17,
"d": {
"f": "from_peer_id", // from peer id
"c": "chat message", // chat message
"m": { "metadata": "value" } // chat metadata
}
}This is a response sent when a peer reconnects.
{
"c": 18,
"d": {
"p": "peer_id" // peer id
}
}This is a response sent when you join a lobby.
{
"c": 19,
"d": {
"l": {
// Lobby Model
},
"p": [{
// Peer Model
}]
}
}This is a response sent when a peer joins.
{
"c": 20,
"d": {
"p": "peer_id" // peer id
}
}This is a response sent when a peer disconnects.
{
"c": 21,
"d": {
"p": "peer_id" // peer id
}
}This is a response sent when a lobby listing event happens.
{
"c": 22,
"d": {
"l": [{
// Lobby Model
}]
}
}This is a response sent when lobby data is updated.
{
"c": 23,
"d": {
"d": { "data": "value" }, // lobby data
"_p": true // true if private data
}
}This is a response sent when a peer is notified.
{
"c": 24,
"d": {
"d": { "data": "value" }, // notify data
"p": "peer_id" // from peer
}
}This is a response sent when a peer data is changed.
{
"c": 25,
"d": {
"d": { "data": "value" }, // peer data
"p": "peer_id", // from peer
"tp": "target_peer_id", // target peer id
"_p": false // true if private data
}
}Applies only to relay mode. to This is a response sent after data was sent.
{
"c": 26,
"d": {}
}Applies only to relay mode. to This is a response sent after notify was sent.
{
"c": 27,
"d": {}
}Commands sent from the client.
Applies only to relay mode. Set data on the lobby.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_LOBBY_DATA
{
"c": 0,
"d": {
"d": { "lobby_data": "value" }, // lobby data
"_p": false, // true means setting private data, false means public data
}
}Applies only to relay mode. Set data on the peer.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_DATA_TO_SENT
{
"c": 1,
"d": {
"d": { "peer_data": "value" }, // peer data
"tp": "peer_id", // target peer
"_p": false, // true means setting private data, false means public data
}
}Applies only to relay mode. Set data on all the peers.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_DATA_TO_SENT
{
"c": 2,
"d": {
"d": { "peer_data": "value" }, // peer data
"_p": false, // true means setting private data, false means public data
}
}Applies only to relay mode. Notify a peer.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_NOTIFY_TO_SENT
{
"c": 3,
"d": {
"d": { "peer_data": "value" }, // notify data
"tp": "peer_id", // peer id
}
}Applies only to relay mode. Notify all peers.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_NOTIFY_TO_SENT
{
"c": 4,
"d": {
"d": { "peer_data": "value" }, // notify data
}
}Applies only to scripted mode. Call a lobby scripted function.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_LOBBY_CALL
{
"c": 5,
"d": {
"f": "echo", // function name
"i": ["param1", 15] // function inputs
}
}Quick join a lobby. Either joins or creates a new lobby.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_LOBBY_CREATED
- RESPONSE_JOINED_LOBBY
{
"c": 6,
"d": {
"n": "lobby_name", // lobby name
"m": 4, // max players
"t": { "lobby_tag": "value" } // lobby tags
}
}Create a new lobby.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_LOBBY_CREATED
{
"c": 7,
"d": {
"n": "lobby_name", // lobby name
"m": 4, // max players
"_p": "password", // password, optional
"t": { "lobby_tag": "value" }, // lobby tags
"s": true, // sealed state
}
}Join a lobby by id.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_JOINED_LOBBY
{
"c": 8,
"d": {
"n": "lobby_name", // lobby name
"m": 4, // max players
"_p": "password", // password, optional
"t": { "lobby_tag": "value" }, // lobby tags
"s": true, // sealed state
}
}Leave current lobby.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_LOBBY_LEFT
{
"c": 9,
"d": {
"n": "lobby_name", // lobby name
"m": 4, // max players
"_p": "password", // password, optional
"t": { "lobby_tag": "value" }, // lobby tags
"s": true, // sealed state
}
}Start receveiving list lobbies responses.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_LOBBY_LIST
{
"c": 10,
"d": {
"l": []
}
}Send a chat message.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_PEER_CHAT
{
"c": 11,
"d": {
"c": "chat_message", // message
"m": { "metadata": "value" } // metadata
}
}Only works if you are host. Update lobby tags.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_LOBBY_TAGS
{
"c": 12,
"d": {
"t": { "tag_key": "tag_value" } // tags
}
}Only works if you are host. Kick a peer.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_PEER_LEFT
{
"c": 13,
"d": {
"p": "peer_to_kick" // peer id
}
}Change your user data.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_PEER_USER_DATA
{
"c": 14,
"d": {
"ud": { "user_data": "value" } // user data
}
}Set yourself ready.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_PEER_READY
{
"c": 15,
"d": {
"p": "peer_id" // peer id
}
}Set yourself unready.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_PEER_UNREADY
{
"c": 16,
"d": {
"p": "peer_id" // peer id
}
}Only works if you are host. Seal the lobby.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_LOBBY_SEALED
{
"c": 17,
"d": {}
}Only works if you are host. Unseal the lobby.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_LOBBY_UNSEALED
{
"c": 18,
"d": {}
}Only works if you are host. Change max players.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_LOBBY_RESIZED
{
"c": 19,
"d": {
"m": 4 // max peers
}
}Only works if you are host. Change the title.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_LOBBY_TITLED
{
"c": 20,
"d": {
"n": "title" // lobby title
}
}Only works if you are host. Change lobby password.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_LOBBY_PASSWORDED
{
"c": 21,
"d": {
"_p": "password" // lobby password
}
}Stop receveiving list lobbies responses.
Receives back one of:
- RESPONSE_ERROR
- RESPONSE_LOBBY_LIST
{
"c": 22,
"d": {
"l": []
}
}Possible errors from server:
ERROR_CANNOT_PARSE_JSON: Cannot parse json.ERROR_PEER_NOT_FOUND: Peer not found.ERROR_PEER_ALREADY_EXISTS: Peer already exists.ERROR_PEER_NOT_IN_A_LOBBY: Peer not in a lobby.ERROR_CANNOT_KICK_SELF: Cannot kick self.ERROR_INVALID_FUNCTION_MSG: Invalid function.ERROR_INVALID_ARGUMENTS: Invalid arguments.ERROR_PEER_IS_IN_A_LOBBY: Peer is in a lobby.ERROR_PEER_NOT_HOST: Peer is not the host.ERROR_PEER_IS_READY: Peer is ready.ERROR_PEER_IS_NOT_READY: Peer is not ready.ERROR_LOBBY_NOT_SEALED: Lobby is not sealed.ERROR_LOBBY_SEALED: Lobby is sealed.ERROR_LOBBY_NOT_FOUND: Lobby not found.ERROR_LOBBY_WRONG_PASSWORD: Wrong password.ERROR_LOBBY_FULL: Lobby is full.ERROR_GAME_NOT_FOUND: Game not found.ERROR_GAME_NOT_RELAY: Game is not in relay mode.ERROR_GAME_NOT_SCRIPTED: Game is not in scripted mode.ERROR_UNKOWN_COMMAND: Unkown command.ERROR_GAME_IS_UNLOADED: Game is being unloaded.ERROR_INVALID_TAGS: Invalid tags.
Possible errors from websocket server:
ERROR_SERVER_TOO_MANY_USERS: Too many users.ERROR_SERVER_FAILED_TO_OPEN_WEBSOCKET: Failed to open websocket.ERROR_SERVER_QUEUE_FULL: Queue full.ERROR_SERVER_RATE_LIMIT: Rate limit exceeded.ERROR_SERVER_RECONNECT_EXPIRED: Reconnect token expired.ERROR_SERVER_RECONNECT_PEER_ID_NOT_FOUND: Reconnect peer id not found.ERROR_SERVER_WRONG_GAMEID: Game id not found.ERROR_SERVER_RECONNECT_CLOSE: Closed with other peer reconnecting over you.ERROR_SERVER_FAILED_TO_RECONNECT: Failed to reconnect.ERROR_SERVER_SHUTTING_DOWN: Server shutting down.