Custom events only fire once

Now, I have created a custom event for my module. However, it only fires once. Sure, I could use connections. But that would mean the person using the module would also have to implement a system to manage these connections on their end. How can I make it so that the events fire multiple times until disconnected?

Example of what I mean:

local t = {}

local function createEvent(eventName)
    local event = Instance.new("BindableEvent")
    event.Name = eventName
    return event
end

local function resetEvent(event) --my attempt on fixing this
    local clone = event:Clone()
    event:Destroy()
    return clone
end

local myEvent = createEvent("Example")
t.CustomEvent = myEvent.Event

local function playerAdded(player)
    player.Chatted:Connect(function(message)
        if message:lower() == "test" then
            myEvent:Fire(player.Name.." said test!")
        end
    end)
end

return t

And example script:

local t = require(script.Parent:WaitForChild("ModuleScript"))
t.CustomEvent:Connect(function(argument)
    print(argument)
end)

try it yourself, it only runs once.

3 Likes

Look, the problem is not with your event, it’s with your chat command. Players’ chat is firing multiple times but the code that fires your event is inside the if statement which is checking if the message is equal to “test”. So, It will fire the event once if you type “test” once, it will fire twice if you type “test” twice and so on.

Here is your corrected code:

local t = {}

local function createEvent(eventName)
    local event = Instance.new("BindableEvent")
    event.Name = eventName
    return event
end

local myEvent = createEvent("Example")
t.CustomEvent = myEvent.Event

local function playerAdded(player)
    player.Chatted:Connect(function(message)
        if message:lower() == "test" then
            myEvent:Fire(player.Name.." said test!")
            print("Event is Fired.")
        end
    end)
end

game.Players.PlayerAdded:Connect(playerAdded)

return t

I made it so every time a player says “test”, the event is fired. This can be verified with the print statement “Event is Fired.”. No need to destroy and clone events. Easy!

This was an example I wrote without testing it… should’ve mentioned that.
My actual code has some problems with firing multiple times, I can’t access my laptop right now so I’ll give the code tomorrow.

1 Like

So sorry for the late response @AndroidSuperShow, I’ve had many issues with the recent bugs from roblox.

Issue: the events fire only once

local metaServerData = {
	ServerData = {};
}
local DataGuard = setmetatable({}, {__index = metaServerData})
local hs = game:GetService("HttpService")
local dss = game:GetService("DataStoreService")
local ds = dss:GetDataStore("Data")
local sessionLocks = dss:GetDataStore("SessionLocked")

type Data = {}
type PlayerName = {}

if not script:WaitForChild("Save Structure", 2) then local ss = Instance.new("Folder") ss.Name = "Save Structure" ss.Parent = script end

local function SessionLock(player: Player)
	local locked
	sessionLocks:UpdateAsync(tostring(player.UserId), function(isLocked)
		locked = isLocked
		return true
	end)
	return locked
end

local function UnsessionLock(player: Player)
	sessionLocks:UpdateAsync(tostring(player.UserId), function(isLocked)
		return false
	end)
end

local function createEvent(eventName)
	local event = Instance.new("BindableEvent")
	event.Name = eventName
	return event
end

local function resetEvent(event)
	local clone = event:Clone()
	event:Destroy()
	return clone
end

function DataGuard:GetData(player: Player | PlayerName)
	if not player then Warn("Missing player.") return end
	if type(player) ~= "string" then if not player:IsA("Player") then return end end
	local plr = type(player) == "string" and game.Players:FindFirstChild(player) or player
	if not DataGuard.ServerData[plr] then return end
	return DataGuard.ServerData[plr]
end

local loadedEvent = createEvent("Loaded")
DataGuard.Loaded = loadedEvent.Event

local function playerAdded(player: Player)
	if DataGuard.ServerData[player] then return end
	local data = {}
	if not script["Save Structure"]:GetChildren()[1] then Warn("No save structure provided!") return end
	for _, v in script["Save Structure"]:GetChildren() do
		if not v:IsA("ValueBase") then continue end
		data[v.Name] = v.Value
	end
	local metadata = {__metatable = "You can just see what's inside the metatable via the code..."; StatsStorage = {}}
	
	local changedEvent = createEvent("Changed")
	metadata.Changed = changedEvent.Event
	
	function metadata:Set(Key: string, NewValue: any)
		if SessionLock(player) then return end
		if Key == nil or NewValue == nil then Warn("Value missing!") return end
		if not metadata.StatsStorage[Key] then Warn("Could not find", Key, "in", player.Name.."'s", "DataStore!") return end
		metadata.StatsStorage[Key] = NewValue
		changedEvent:Fire(Key, NewValue)
		changedEvent = resetEvent(changedEvent)
		metadata.Changed = changedEvent.Event
	end
	
	function metadata:Save()
		if SessionLock(player) then return end
		local success = false
		for attempts = 1, 5 do
			success = pcall(function()
				ds:SetAsync(tostring(player.UserId), hs:JSONEncode(metadata.StatsStorage))
			end)
			if success then break end
		end
		if not success then Warn("Could not save", player.Name.."'s data!") end
	end
	
	function metadata:GetLastSave(): Data | boolean
		local returned = false
		pcall(function()
			local saved = ds:GetAsync(tostring(player.UserId))
			if saved then
				returned = hs:JSONDecode(saved)
			end
		end)
		return returned
	end
	
	function metadata:GetValue(Key: string): any
		print(metadata.StatsStorage)
		if Key == nil then Warn("Value missing!") return end
		if not metadata.StatsStorage[Key] then Warn("Could not find", Key, "in data!") return end
		return metadata.StatsStorage[Key]
	end
	
	metadata.__index = metadata
	metadata.__tostring = function(t)
		local s = "{"
		local i = 0
		for k, v in metadata.StatsStorage do
			i += 1
			s = string.format(s..'["%s"] = %s', k, v)
			if i ~= #metadata.StatsStorage then
				s = s..", "
			end
		end
		s = string.sub(s, 1, s:len() - 2)
		s = s.."}"
		return s
	end
	
	local lastSave = metadata:GetLastSave()
	
	if lastSave == true then
		data = lastSave
	end
	
	for k, v in data do
		metadata.StatsStorage[k] = v
	end
	
	data = {}
	setmetatable(data, metadata)
	DataGuard.ServerData[player] = data
	loadedEvent:Fire(player)
	loadedEvent = resetEvent(loadedEvent)
	DataGuard.Loaded = loadedEvent.Event
end

for _, v in game.Players:GetPlayers() do
	playerAdded(v)
end

game.Players.PlayerAdded:Connect(playerAdded)

game.Players.PlayerRemoving:Connect(function(player)
	DataGuard.ServerData[player]:Save()
	UnsessionLock(player)
	DataGuard.ServerData[player] = nil
end)

game:BindToClose(function()
	for _, v in game.Players:GetPlayers() do
		DataGuard.ServerData[v]:Save()
	end
	task.wait(5)
end)
	
function Warn(...)
	warn("DataGuard:", ...)
end

local elapsed = 0
game:GetService("RunService").Heartbeat:Connect(function(delta)
	elapsed = math.clamp(elapsed + delta, 0, 60)
	if elapsed ~= 60 then return end
	elapsed = 0
	if not DataGuard.AutoSaveEnabled then return end
	for _, v in game.Players:GetPlayers() do
		DataGuard.ServerData[v]:Save()
	end
end)

return DataGuard

I’ve added resetEvent to try to fix the problem but it does the same thing.

The problem with using a BindableEvent is that it is destroyed when you do event:Destroy(). While creating a new one and reassigning it to our module’s DataGuard.Loaded works, it doesn’t immediately reflect on the scripts already connected to the event - they’re still listening to the old, destroyed event.

Using a BindableEvent for your use case may not best suit your needs because BindableEvent objects are intended for one-time uses.

A more suitable alternative would be to use RBXScriptSignal and RBXScriptConnection. They have a built-in system for connecting and disconnecting, meaning you don’t need to create a new event every time it’s fired.

Here is an example of how you might use them using your playerAdded function. We create a connection, fire the event, then disconnect. Next time the function is called, we repeat.

local function playerAdded(player: Player)
    if DataGuard.ServerData[player] then return end
    -- ... (other code implementation)
    local loaded = Instance.new("BindableEvent")
    DataGuard.Loaded = loaded.Event
    -- More code above
    loaded:Fire(player)
    loaded:Destroy() -- Event will be destroyed but it fired before
    -- More code below...
end

Here the event will run more than once, because an event’s :Fire() method invokes all connected functions, but does not disconnect them.
Remember to destroy each connection once you’re done with it.

Remember, optimization is key! If you’re firing events very often, it may be worth reusing them versus creating a new one each time. Ideally, you’d have one persistent event and connection for each user, and manage what you transmit through the event (like specific changes, updates, etc.) instead of creating and destroying events unnecessarily.

This looks AI generated, RBXScriptSignal and RBXScriptConnection are from Bindable Events. The “sample code” includes using BindableEvents, not the aformentioned types exclusively. Bindable Events are 100% intended to be re-used.


I think your events are getting garbage collected. You don’t save a reference to the BindableEvent, but instead the .Event property. Roblox may be cleaning up the unsaved BindableEvent and therefore it’s connections. It’s kind of a long shot I feel the garbage collector should be smart enough to find it’s way around this issue.

Try storing t.CustomEvent = myEvent and connecting it via t.CustomEvent.Event:Conect(

1 Like

should I just have nothing in the function?

this does seem reasonable

it is a useless function. the name of the bindable event doesn’t need to be set since it’s not parented to anything; you only use the variable name.

Sorry for the late response,
I’ve added eventName.Event:Connect(function() end) underneath the part the event is defined, no change.

just to make sure, does attaching an empty event make it un-garbagecollectable