Pub/Sub Events
The Pub/Sub (Publish/Subscribe) system allows you to broadcast events across all game servers in real-time.
Overview
Traditional Roblox communication is limited to:
- Same server only (RemoteEvents, BindableEvents)
- No easy way to communicate between servers
Raddish solves this with a built-in Pub/Sub system that uses MessagingService under the hood, with automatic rate limiting and caching.
Basic Usage
Subscribe to Events
lua
local Raddish = require(game.ReplicatedStorage.Raddish)
-- Subscribe to a channel
Raddish.Subscribe("player:events", function(data)
print("Received event:", data.event, data.player)
end)Publish Events
lua
-- Publish an event to all servers
Raddish.Publish("player:events", {
event = "player_joined",
player = "VerifiedHawaii",
timestamp = os.time()
})Advanced Options
Subscribe Options
lua
-- Local-only subscription (no cross-server)
Raddish.Subscribe("local:events", function(data)
print("Local event:", data)
end, {
external = false -- Only receive events from this server
})
-- Unsubscribe
local unsubscribe = Raddish.Subscribe("temp:events", function(data)
print(data)
end)
-- Later...
unsubscribe() -- Stop listeningPublish Options
lua
-- Publish with options
Raddish.Publish("player:events", {
event = "achievement",
player = "VerifiedHawaii"
}, {
external = true, -- Send to other servers (default)
cache = true -- Cache event history (default)
})
-- Local-only publish (same server)
Raddish.Publish("local:notification", {
message = "Server restarting in 5 minutes"
}, {
external = false
})Event History
Events are automatically cached for a short period.
lua
-- Get last 50 events from a channel
local history = Raddish.GetHistory("player:events", 50)
for _, event in ipairs(history) do
print("Event:", event.data)
print("Timestamp:", event.timestamp)
print("ID:", event.id)
endUse Cases
Global Announcements
lua
-- Announce to all servers
function announceGlobal(message)
Raddish.Publish("global:announcement", {
message = message,
timestamp = os.time()
})
end
-- All servers listen
Raddish.Subscribe("global:announcement", function(data)
-- Show announcement to all players on this server
for _, player in ipairs(game.Players:GetPlayers()) do
game.ReplicatedStorage.ShowAnnouncement:FireClient(player, data.message)
end
end)
-- Usage
announceGlobal("Server maintenance in 10 minutes!")Cross-Server PvP Events
lua
-- Player wins PvP match
Raddish.Publish("pvp:victory", {
winner = player.Name,
loser = opponent.Name,
score = "3-2",
serverId = game.JobId
})
-- All servers listen and update global stats
Raddish.Subscribe("pvp:victory", function(data)
print(data.winner .. " defeated " .. data.loser)
-- Update global leaderboard
Raddish.ZIncrBy("leaderboard:pvp", 10, data.winner)
end)Server Status Monitoring
lua
-- Each server reports status every 30 seconds
task.spawn(function()
while true do
Raddish.Publish("server:status", {
serverId = game.JobId,
players = #game.Players:GetPlayers(),
maxPlayers = game.Players.MaxPlayers,
memory = game:GetService("Stats"):GetTotalMemoryUsageMb(),
uptime = workspace.DistributedGameTime
})
task.wait(30)
end
end)
-- Central monitoring server
Raddish.Subscribe("server:status", function(data)
-- Store server stats
Raddish.HSet("servers:" .. data.serverId, "players", data.players)
Raddish.HSet("servers:" .. data.serverId, "memory", data.memory)
Raddish.HSet("servers:" .. data.serverId, "lastUpdate", os.time())
-- Check for unhealthy servers
if data.memory > 3000 then
warn("Server " .. data.serverId .. " has high memory usage!")
end
end)Real-Time Trading
lua
-- Player lists item for trade
function listItemForTrade(seller, item, price)
Raddish.Publish("trading:new_listing", {
seller = seller.UserId,
sellerName = seller.Name,
item = item,
price = price,
listingId = game:GetService("HttpService"):GenerateGUID(false),
timestamp = os.time()
})
end
-- All servers show new listings
Raddish.Subscribe("trading:new_listing", function(data)
-- Add to trading board
Raddish.ZAdd("trading:active", data.timestamp, data.listingId)
Raddish.HSet("listing:" .. data.listingId, "seller", data.seller)
Raddish.HSet("listing:" .. data.listingId, "item", data.item)
Raddish.HSet("listing:" .. data.listingId, "price", data.price)
print("New listing:", data.sellerName, "selling", data.item, "for", data.price)
end)
-- Trade completed
Raddish.Subscribe("trading:completed", function(data)
-- Remove from active listings
Raddish.ZRem("trading:active", data.listingId)
Raddish.Delete("listing:" .. data.listingId)
end)Matchmaking Queue
lua
-- Player joins queue
function joinMatchmaking(player, gameMode)
Raddish.Publish("matchmaking:join", {
userId = player.UserId,
username = player.Name,
gameMode = gameMode,
skillRating = getPlayerSkill(player),
timestamp = os.time()
})
end
-- Matchmaking server listens
Raddish.Subscribe("matchmaking:join", function(data)
-- Add to queue
Raddish.ZAdd("queue:" .. data.gameMode, data.skillRating, data.userId)
-- Check if we can make a match
local queueSize = Raddish.ZCard("queue:" .. data.gameMode)
if queueSize >= 2 then
-- Get players with similar skill
local players = Raddish.ZRange("queue:" .. data.gameMode, 1, 2, true)
-- Create match
Raddish.Publish("matchmaking:match_found", {
gameMode = data.gameMode,
players = players,
serverId = game.JobId
})
-- Remove from queue
for _, p in ipairs(players) do
Raddish.ZRem("queue:" .. data.gameMode, p.member)
end
end
end)
-- Players receive match notification
Raddish.Subscribe("matchmaking:match_found", function(data)
print("Match found!", data.gameMode)
-- Teleport players to game server
end)Live Events
lua
-- Start a live event
Raddish.Publish("event:start", {
eventName = "Boss Raid",
location = "Dungeon",
difficulty = "Hard",
rewards = {"Legendary Sword", "5000 Gold"}
})
-- All servers participate
Raddish.Subscribe("event:start", function(data)
-- Notify all players on this server
for _, player in ipairs(game.Players:GetPlayers()) do
game.ReplicatedStorage.EventNotification:FireClient(player, {
title = data.eventName,
description = "Live event started at " .. data.location
})
end
-- Start event on this server
startEvent(data.eventName, data.location)
end)
-- Event progress updates
Raddish.Subscribe("event:progress", function(data)
-- data.progress = 0-100
updateEventUI(data.eventName, data.progress)
end)
-- Event completed
Raddish.Publish("event:complete", {
eventName = "Boss Raid",
winners = {"Player1", "Player2", "Player3"},
completionTime = 300 -- seconds
})Rate Limiting
Raddish automatically handles MessagingService rate limits:
lua
-- MessagingService limit: 150 + 60 * num_players per minute
-- Raddish batches messages to stay under the limit
-- Get subscriber count
local count = Raddish.GetSubscriberCount("player:events")
print("Subscribers:", count)
-- Unsubscribe all from channel
Raddish.UnsubscribeAll("old:channel")Best Practices
1. Use Namespaces
lua
-- Good: Clear namespacing
Raddish.Publish("player:joined", data)
Raddish.Publish("player:left", data)
Raddish.Publish("trade:completed", data)
Raddish.Publish("event:started", data)
-- Bad: No structure
Raddish.Publish("playerjoined", data)
Raddish.Publish("trade", data)2. Include Timestamps
lua
-- Always include timestamps for debugging
Raddish.Publish("player:action", {
userId = player.UserId,
action = "purchase",
item = "sword",
timestamp = os.time() -- Important!
})3. Keep Payloads Small
lua
-- Good: Small payload
Raddish.Publish("player:update", {
userId = 12345,
score = 1000
})
-- Bad: Large payload (>1KB can fail)
Raddish.Publish("player:update", {
userId = 12345,
fullInventory = {...}, -- Too large!
allAchievements = {...},
completeHistory = {...}
})4. Handle Failures Gracefully
lua
Raddish.Subscribe("important:event", function(data)
local success, err = pcall(function()
-- Process event
processEvent(data)
end)
if not success then
warn("Failed to process event:", err)
-- Maybe queue for retry
end
end)Debugging
lua
-- Enable debug logging
Raddish.Subscribe("*", function(data, channel)
print("[DEBUG] Channel:", channel)
print("[DEBUG] Data:", data)
end)
-- Monitor all events
Raddish.Subscribe("debug:all", function(data)
print(os.date(), data)
end)