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)
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.
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
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(
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.