Skip to content

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 listening

Publish 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)
end

Use 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)

"Beyond Boundaries. Beyond Imagination."