Skip to content

Pub/Sub Examples

Real-world examples of using Raddish's Pub/Sub system for cross-server communication in your Roblox game.

Global Announcements

Send announcements to all servers at once.

lua
local Raddish = require(game.ReplicatedStorage.Raddish)

-- Server Script: Admin command to announce
local function announceToAllServers(message, sender)
    Raddish.Publish("global:announcement", {
        message = message,
        sender = sender,
        timestamp = os.time()
    })
end

-- All servers listen for announcements
Raddish.Subscribe("global:announcement", function(data)
    -- Show to all players on this server
    for _, player in ipairs(game.Players:GetPlayers()) do
        game.ReplicatedStorage.Remotes.ShowAnnouncement:FireClient(player, {
            title = "Server Announcement",
            message = data.message,
            from = data.sender
        })
    end
    
    print("[Announcement]", data.sender .. ":", data.message)
end)

-- Admin command
game.Players.PlayerAdded:Connect(function(player)
    player.Chatted:Connect(function(message)
        if player:GetRankInGroup(123456) >= 250 then -- Admin rank
            if message:sub(1, 10) == "/announce " then
                local announcement = message:sub(11)
                announceToAllServers(announcement, player.Name)
            end
        end
    end)
end)

Cross-Server Trading System

Allow players to trade across different servers.

lua
local Raddish = require(game.ReplicatedStorage.Raddish)
local HttpService = game:GetService("HttpService")

-- List item for trade (broadcasts to all servers)
function listItemForTrade(seller, itemName, price)
    local listingId = HttpService:GenerateGUID(false)
    
    Raddish.Publish("trading:new_listing", {
        listingId = listingId,
        sellerId = seller.UserId,
        sellerName = seller.Name,
        itemName = itemName,
        price = price,
        serverJobId = game.JobId,
        timestamp = os.time()
    })
    
    -- Store locally
    Raddish.HSet("listing:" .. listingId, "seller", seller.UserId)
    Raddish.HSet("listing:" .. listingId, "item", itemName)
    Raddish.HSet("listing:" .. listingId, "price", price)
    Raddish.Expire("listing:" .. listingId, 3600) -- Expire after 1 hour
    
    return listingId
end

-- All servers listen for new listings
Raddish.Subscribe("trading:new_listing", function(data)
    -- Add to global trading board
    Raddish.ZAdd("trading:active", data.timestamp, data.listingId)
    
    -- Notify players on this server
    for _, player in ipairs(game.Players:GetPlayers()) do
        game.ReplicatedStorage.Remotes.NewTradeListing:FireClient(player, {
            seller = data.sellerName,
            item = data.itemName,
            price = data.price,
            listingId = data.listingId
        })
    end
    
    print("[Trading] New listing:", data.sellerName, "selling", data.itemName, "for", data.price)
end)

-- Purchase item (cross-server)
function purchaseItem(buyer, listingId)
    Raddish.Publish("trading:purchase_request", {
        listingId = listingId,
        buyerId = buyer.UserId,
        buyerName = buyer.Name,
        timestamp = os.time()
    })
end

-- Listen for purchase requests
Raddish.Subscribe("trading:purchase_request", function(data)
    local listing = Raddish.HGetAll("listing:" .. data.listingId)
    
    if listing and listing.seller then
        -- Process on seller's server
        local seller = game.Players:GetPlayerByUserId(tonumber(listing.seller))
        
        if seller then
            -- Transfer item and coins
            Raddish.Publish("trading:completed", {
                listingId = data.listingId,
                sellerId = listing.seller,
                buyerId = data.buyerId,
                itemName = listing.item,
                price = listing.price
            })
            
            -- Remove listing
            Raddish.ZRem("trading:active", data.listingId)
            Raddish.Delete("listing:" .. data.listingId)
        end
    end
end)

-- Listen for completed trades
Raddish.Subscribe("trading:completed", function(data)
    print("[Trading] Trade completed:", data.itemName, "sold for", data.price)
    
    -- Update UI for all players
    for _, player in ipairs(game.Players:GetPlayers()) do
        game.ReplicatedStorage.Remotes.TradeCompleted:FireClient(player, data)
    end
end)

Live Events System

Start events that run across all servers simultaneously.

lua
local Raddish = require(game.ReplicatedStorage.Raddish)

-- Start a global event
function startGlobalEvent(eventName, duration, rewards)
    Raddish.Publish("events:start", {
        eventName = eventName,
        duration = duration,
        rewards = rewards,
        startTime = os.time(),
        endTime = os.time() + duration
    })
end

-- All servers listen for event start
Raddish.Subscribe("events:start", function(data)
    print("[Event] Starting:", data.eventName)
    
    -- Notify all players
    for _, player in ipairs(game.Players:GetPlayers()) do
        game.ReplicatedStorage.Remotes.EventStarted:FireClient(player, {
            title = data.eventName,
            duration = data.duration,
            rewards = data.rewards
        })
    end
    
    -- Start event logic on this server
    spawnEventBosses(data.eventName)
    
    -- Schedule event end
    task.delay(data.duration, function()
        endEvent(data.eventName)
    end)
end)

-- Update event progress globally
function updateEventProgress(eventName, progress)
    Raddish.Publish("events:progress", {
        eventName = eventName,
        progress = progress, -- 0-100
        timestamp = os.time()
    })
end

Raddish.Subscribe("events:progress", function(data)
    -- Update progress bar for all players
    for _, player in ipairs(game.Players:GetPlayers()) do
        game.ReplicatedStorage.Remotes.EventProgress:FireClient(player, data.progress)
    end
end)

-- Event completion
function completeEvent(eventName, winners)
    Raddish.Publish("events:complete", {
        eventName = eventName,
        winners = winners,
        completionTime = os.time()
    })
end

Raddish.Subscribe("events:complete", function(data)
    print("[Event] Completed:", data.eventName)
    print("Winners:", table.concat(data.winners, ", "))
    
    -- Award winners across all servers
    for _, winnerId in ipairs(data.winners) do
        local player = game.Players:GetPlayerByUserId(winnerId)
        if player then
            giveReward(player, "Legendary Sword")
        end
    end
end)

-- Example: 2x XP Event
startGlobalEvent("Double XP Weekend", 172800, {"2x XP", "Exclusive Badge"}) -- 48 hours

Server Status Monitoring

Monitor all game servers from a central dashboard.

lua
local Raddish = require(game.ReplicatedStorage.Raddish)

-- Each server reports its status every 30 seconds
task.spawn(function()
    while true do
        local stats = {
            serverId = game.JobId,
            placeId = game.PlaceId,
            playerCount = #game.Players:GetPlayers(),
            maxPlayers = game.Players.MaxPlayers,
            memory = game:GetService("Stats"):GetTotalMemoryUsageMb(),
            ping = game:GetService("Stats").DataReceiveKbps,
            uptime = workspace.DistributedGameTime,
            region = game.PrivateServerId ~= "" and "Private" or "Public",
            timestamp = os.time()
        }
        
        Raddish.Publish("monitoring:server_status", stats)
        
        task.wait(30)
    end
end)

-- Monitoring server collects all server data
Raddish.Subscribe("monitoring:server_status", function(data)
    -- Store server stats
    Raddish.HSet("servers:" .. data.serverId, "players", data.playerCount)
    Raddish.HSet("servers:" .. data.serverId, "memory", data.memory)
    Raddish.HSet("servers:" .. data.serverId, "uptime", data.uptime)
    Raddish.HSet("servers:" .. data.serverId, "lastUpdate", os.time())
    Raddish.Expire("servers:" .. data.serverId, 120) -- Expire if no update in 2 min
    
    -- Check for issues
    if data.memory > 3000 then
        warn("[Monitoring] High memory on server:", data.serverId)
        
        -- Alert to Discord
        Raddish.SendDiscordWebhook("YOUR_WEBHOOK_URL", {
            title = "⚠️ High Memory Alert",
            description = "Server " .. data.serverId .. " is using " .. data.memory .. " MB",
            color = 0xFFAA00,
            fields = {
                {name = "Players", value = tostring(data.playerCount), inline = true},
                {name = "Uptime", value = tostring(math.floor(data.uptime/60)) .. " min", inline = true}
            }
        })
    end
    
    if data.playerCount == data.maxPlayers then
        print("[Monitoring] Server full:", data.serverId)
    end
end)

-- Get all active servers
function getActiveServers()
    local serverKeys = Raddish.Keys("servers:*")
    local servers = {}
    
    for _, key in ipairs(serverKeys) do
        local serverData = Raddish.HGetAll(key)
        table.insert(servers, serverData)
    end
    
    return servers
end

-- Get total player count across all servers
function getTotalPlayers()
    local servers = getActiveServers()
    local total = 0
    
    for _, server in ipairs(servers) do
        total = total + (tonumber(server.players) or 0)
    end
    
    return total
end

print("Total players across all servers:", getTotalPlayers())

Matchmaking Queue

Cross-server matchmaking with skill-based matching.

lua
local Raddish = require(game.ReplicatedStorage.Raddish)

-- Player joins matchmaking queue
function joinQueue(player, gameMode)
    local skillRating = getPlayerSkill(player) -- Your skill calculation
    
    Raddish.Publish("matchmaking:join", {
        userId = player.UserId,
        username = player.Name,
        gameMode = gameMode,
        skillRating = skillRating,
        serverId = game.JobId,
        timestamp = os.time()
    })
    
    print(player.Name, "joined", gameMode, "queue with rating", skillRating)
end

-- Matchmaking server handles queue
local QUEUE_CHECK_INTERVAL = 2 -- Check every 2 seconds
local SKILL_RANGE = 100 -- ±100 rating for matching

Raddish.Subscribe("matchmaking:join", function(data)
    -- Add to queue
    Raddish.ZAdd("queue:" .. data.gameMode, data.skillRating, data.userId)
    
    -- Store player data
    Raddish.HSet("queue:player:" .. data.userId, "username", data.username)
    Raddish.HSet("queue:player:" .. data.userId, "skillRating", data.skillRating)
    Raddish.HSet("queue:player:" .. data.userId, "serverId", data.serverId)
    Raddish.Expire("queue:player:" .. data.userId, 300) -- 5 min timeout
    
    print("[Matchmaking]", data.username, "joined queue")
end)

-- Process matchmaking queue
task.spawn(function()
    while true do
        task.wait(QUEUE_CHECK_INTERVAL)
        
        local gameModes = {"casual", "ranked", "competitive"}
        
        for _, gameMode in ipairs(gameModes) do
            local queueSize = Raddish.ZCard("queue:" .. gameMode)
            
            if queueSize >= 2 then
                -- Get all players in queue
                local players = Raddish.ZRange("queue:" .. gameMode, 1, -1, true)
                
                -- Try to match players with similar skill
                for i = 1, #players - 1 do
                    local player1 = players[i]
                    
                    for j = i + 1, #players do
                        local player2 = players[j]
                        
                        -- Check if skill ratings are close
                        if math.abs(player1.score - player2.score) <= SKILL_RANGE then
                            -- Create match!
                            createMatch(gameMode, {player1.member, player2.member})
                            
                            -- Remove from queue
                            Raddish.ZRem("queue:" .. gameMode, player1.member)
                            Raddish.ZRem("queue:" .. gameMode, player2.member)
                            
                            break
                        end
                    end
                end
            end
        end
    end
end)

function createMatch(gameMode, playerIds)
    local matchId = game:GetService("HttpService"):GenerateGUID(false)
    
    Raddish.Publish("matchmaking:match_found", {
        matchId = matchId,
        gameMode = gameMode,
        players = playerIds,
        serverId = game.JobId,
        timestamp = os.time()
    })
    
    print("[Matchmaking] Match created:", matchId)
end

-- Players receive match notification
Raddish.Subscribe("matchmaking:match_found", function(data)
    for _, userId in ipairs(data.players) do
        local player = game.Players:GetPlayerByUserId(tonumber(userId))
        
        if player then
            -- Teleport to match server
            game.ReplicatedStorage.Remotes.MatchFound:FireClient(player, {
                matchId = data.matchId,
                gameMode = data.gameMode
            })
            
            print(player.Name, "matched! Teleporting...")
        end
    end
end)

-- Player leaves queue
function leaveQueue(player, gameMode)
    Raddish.Publish("matchmaking:leave", {
        userId = player.UserId,
        gameMode = gameMode
    })
end

Raddish.Subscribe("matchmaking:leave", function(data)
    Raddish.ZRem("queue:" .. data.gameMode, data.userId)
    Raddish.Delete("queue:player:" .. data.userId)
end)

Global Chat System

Cross-server chat channels.

lua
local Raddish = require(game.ReplicatedStorage.Raddish)
local TextService = game:GetService("TextService")

-- Send message to global chat
function sendGlobalMessage(player, message, channel)
    channel = channel or "global"
    
    -- Filter message
    local success, filteredMessage = pcall(function()
        return TextService:FilterStringAsync(message, player.UserId):GetNonChatStringForBroadcastAsync()
    end)
    
    if not success then
        return
    end
    
    Raddish.Publish("chat:" .. channel, {
        userId = player.UserId,
        username = player.Name,
        message = filteredMessage,
        channel = channel,
        timestamp = os.time()
    })
    
    -- Store in chat history
    Raddish.RPush("chat:history:" .. channel, {
        userId = player.UserId,
        username = player.Name,
        message = filteredMessage,
        timestamp = os.time()
    })
    
    -- Keep only last 100 messages
    if Raddish.LLen("chat:history:" .. channel) > 100 then
        Raddish.LPop("chat:history:" .. channel)
    end
end

-- All servers listen for chat messages
Raddish.Subscribe("chat:global", function(data)
    -- Display to all players on this server
    for _, player in ipairs(game.Players:GetPlayers()) do
        game.ReplicatedStorage.Remotes.GlobalChatMessage:FireClient(player, {
            username = data.username,
            message = data.message,
            timestamp = data.timestamp
        })
    end
end)

-- VIP channel
Raddish.Subscribe("chat:vip", function(data)
    for _, player in ipairs(game.Players:GetPlayers()) do
        if player:GetAttribute("IsVIP") then
            game.ReplicatedStorage.Remotes.VIPChatMessage:FireClient(player, {
                username = data.username,
                message = data.message
            })
        end
    end
end)

-- Get chat history when player joins
function loadChatHistory(player, channel)
    local history = Raddish.LRange("chat:history:" .. channel, -50, -1) -- Last 50 messages
    game.ReplicatedStorage.Remotes.LoadChatHistory:FireClient(player, history)
end

game.Players.PlayerAdded:Connect(function(player)
    loadChatHistory(player, "global")
end)

PvP Statistics Tracking

Track PvP stats across all servers.

lua
local Raddish = require(game.ReplicatedStorage.Raddish)

-- Player wins PvP match
function recordPvPWin(winner, loser, gameMode)
    Raddish.Publish("pvp:match_result", {
        winnerId = winner.UserId,
        winnerName = winner.Name,
        loserId = loser.UserId,
        loserName = loser.Name,
        gameMode = gameMode,
        timestamp = os.time()
    })
end

-- All servers update global stats
Raddish.Subscribe("pvp:match_result", function(data)
    -- Update winner stats
    Raddish.IncrementWithCache(
        {UserId = data.winnerId},
        "PvP_Wins",
        1,
        0
    )
    
    Raddish.IncrementWithCache(
        {UserId = data.winnerId},
        "PvP_Rating",
        25,
        1000
    )
    
    -- Update loser stats
    Raddish.IncrementWithCache(
        {UserId = data.loserId},
        "PvP_Losses",
        1,
        0
    )
    
    Raddish.IncrementWithCache(
        {UserId = data.loserId},
        "PvP_Rating",
        -15,
        1000
    )
    
    -- Update global leaderboard
    local winnerRating = Raddish.GetWithCache({UserId = data.winnerId}, "PvP_Rating", 1000)
    Raddish.ZAdd("leaderboard:pvp", winnerRating, data.winnerId)
    
    local loserRating = Raddish.GetWithCache({UserId = data.loserId}, "PvP_Rating", 1000)
    Raddish.ZAdd("leaderboard:pvp", loserRating, data.loserId)
    
    print("[PvP]", data.winnerName, "defeated", data.loserName)
end)

Best Practices

  • Use clear channel naming conventions (category:action)
  • Always include timestamps in your data
  • Keep payloads small (<1KB recommended)
  • Handle subscription errors with pcall
  • Use external = false for server-local events

Rate Limits

MessagingService has a limit of 150 + 60 * number_of_players messages per minute. Raddish automatically batches messages to stay under this limit.

"Beyond Boundaries. Beyond Imagination."