I wanted my module script to use the Bindable Events within itself as a homemade Changed event.
However, when the module script changes something and fires the bindable event, no local script detects the change. Which is odd because I can make a local script fire the event, AND listen to it and it will work just fine. It would seem like JUST the module script cant call it.
I tried moving the events to replicated storage and yielding to the call. Nothing. Are bindable events just not possible for client-side module scripts?
The bindable event, PlayerChanged, is child to ModuleScript. then i just have a testing script that’s supposed to change the data. The module script makes the adjustment and “fires” the event. but the script thats listening will detect nothing.
here is the code for the testing local script:
local PlayerChanged= script.Parent:WaitForChild("ModuleScript1"):WaitForChild("PlayerChanged")
local Module = require(script.Parent:WaitForChild("ModuleScript1"))
local Testbutton = script.Parent.Parent:WaitForChild("ScreenGui"):WaitForChild("TextButton")
Testbutton.Activated:Connect(function()--I attached the call to a clickable button. just so that i could REALLY spam it
Module.Player().Offset(3,10)
end)
PlayerChanged.Event:Connect(function(PlayerData)
print("CHANGE DETECTED!!!") --this is never printed :(
print(PlayerData)
end)
this is a snippet of the module script:
function Player.SetSettings(Index,Value)
if not CheckParameters({Index,Value},2) then return{3,"ERROR. MISSING PARAMETERS"}end --ignore this. its just validating that parameters were sent
ChangeSaveStatus:FireServer("PlayerDataStoreStatus")--ignore this
PlayerData[1][11][Index]=Value--the actual change taking place. It works
PlayerChanged:Fire(PlayerData)--never called
return {1,PlayerData}--code 1 and updated table returned
end
I would REALLY like to keep this approach because the code is so much cleaner than using Attributes to detect changes across the client. Any advice or help would be amazing
For the BindableEvent to fire, you need to call the SetSettings() function of your module. In this case it should be called inside the Module.Player() or Module.Player().Offset() function. Please check that you are indeed calling the SetSettings() function in one of these.
If it still does not work, you could post the code of these two functions.
Apologies, but it IS being called, just not in any given script. Its being called from a script who’s purpose is to test the module script, hence the return codes.
I have no doubt the way you programmed it that a module script could be fired during a callback. However, this is already known and doesn’t address the problem. module scripts cannot FIRE the event. you fired the event within local scripts and handled the event inside as well. which i already stated.
--Module script inside ReplicatedStorage.
local module = {}
local replicated = script.Parent --ReplicatedStorage script.
local bindable = replicated:WaitForChild("Event")
function module.fireBindable(...)
bindable:Fire(...)
end
return module
--Local script inside StarterGui.
local starterGui = script.Parent --StarterGui script.
local replicated = game:GetService("ReplicatedStorage")
local module = replicated:WaitForChild("ModuleScript")
module = require(module)
task.wait(1)
module.fireBindable(math.pi)
--Local script inside StarterPlayerScripts.
local starterPlayer = script.Parent.Parent --StarterPlayer script.
local replicated = game:GetService("ReplicatedStorage")
local bindable = replicated:WaitForChild("Event")
bindable.Event:Connect(function(...)
print(...)
end)
This, as expected, prints the math.pi constant to the console.
There’s a good chance you’re encountering a racing condition, in which the instance method “Fire()” is called on the BindableEvent before a connection is made to its RBXScriptSignal object named “Event”.
I avoided that in the scripts I provided by yielding for a second before firing the BindableEvent instance.
As @Forummer said, it’s likely a racing condition.
Rather than using a bindable (since it has such a drawback), use a Pure Signal implementation. It should work just about the same. I’ve also figured out a method to retain the signal if required multiple times (although not very ideal…)
Here’s the code: (read notes please)
-- with this implementation, a module can be required in
-- multiple places, and still retain the same Changed event.
local module = {}
local ID = script:GetAttribute("ID")
local Signal = nil
if not ID then -- multiple modules with changed events can be made with an ID
-- if we don't do this, we're setting a constant key to _G (_G is not recommended but it'll do)
ID = game:GetService("HttpService"):GenerateGUID(false):sub(1,6)
-- we only need a limited string (they're unique enough)
-- and we save memory this way.
script:SetAttribute("ID", ID)
end
Signal = _G[ID]
if not Signal then
-- make sure you change this to the proper path
Signal = require(yoursignalmodulepath).new()
_G[ID] = Signal
end
module.Changed = Signal
--example
function module:SetParent(Parent)
assert(typeof(Parent) == "Instance", "Invalid argument #1 to module:SetParent(Instance)")
script.Parent = Parent
module.Changed:Fire("Parent", Parent) -- won't just randomly throw errors.
end
return module
Signal module if you need it
local signal = {
DisconnectYieldMax = 1.5,
Debug = true
}
local integrated_proto,standalone_proto = {},{}
local Thread = nil
local function ExecThread(Exec, ...)
local CurrentThread = Thread
Thread = nil
Exec(...)
Thread = CurrentThread
end
local function RunInThread(...)
ExecThread(...)
while true do
ExecThread(coroutine.yield())
end
end
local function allocate(...)
local Args = table.pack(...)
local Argc = table.getn(Args)
if Argc == 1 and typeof(Args[1]) == "table" then
local Arg = Args[1]
local ArgS = table.getn(Args[1])
local New = table.create(ArgS)
for Index, Value in ipairs(Arg) do
New[Index] = Value
end
return New
elseif Argc > 1 then
local New = table.create(Argc)
for Index, Value in ipairs(Args) do
New[Index] = Value
end
return New
end
end
local Connects = {}
Connects.__tostring = function() return "PureConnection" end
Connects.__index = Connects
function Connects:Disconnect()
assert(self[2], "PureConnection cannot be disconnected twice.")
self[2]=nil
if self[1][1] == self then
self[1][1] = self[3]
else
task.spawn(function()
local Top = self[1][1]
while Top and Top[3] ~= self do
Top = Top[3]
end
if Top then
Top[3] = self[3]
end
end)
end
end
local function BuildConnection(Signal, Handle, Queue)
local Connection = allocate({
[1] = Signal, -- Main Signal
[2] = Handle, -- Connection Handle(r)
[3] = Queue -- Next connection in execution stack
})
return setmetatable(Connection, Connects)
end
function signal.new()
local sig = allocate({
[1] = false
})
return setmetatable(sig, {
__index = integrated_proto,
__newindex = function(self, k, v)
if typeof(k) == "number" and k <= 2 then
error("Attempt to overwrite internal values of PureSignal!", 2)
else
self[k] = v
end
end,
__metatable = "Locked!",
__tostring = function() return "[LuaU] PureSignal" end
})
end
function integrated_proto:Connect(Handle)
assert(typeof(Handle) == "function", ("Invalid argument #1 to PureSignal:Connect(), expects function, got %s"):format(typeof(Handle)))
local Connector = BuildConnection(self, Handle, self[1])
self[1] = Connector
return Connector
end
function integrated_proto:Fire(...)
local StackHead = self[1]
task.spawn(function(Top, ...)
while Top do
if Top[2] then
Thread = if Thread then Thread else coroutine.create(RunInThread)
task.spawn(Thread, Top[2], ...)
end
Top = Top[3]
end
end, StackHead, ...)
end
function integrated_proto:Wait()
local ThreadParent, Temp = coroutine.running()
Temp = self:Connect(function(...)
Temp:Disconnect()
task.spawn(ThreadParent, ...)
end)
return coroutine.yield()
end
return signal
It includes:
:Wait() – yields and returns the arguments to the :Fire() call that broke yield.
Hm, your code might work. But I also think that it only works because it’s a connection between server and client. Like a remote event that uses bindable event. What I was doing was strictly client-sided. I will test this theory and let you know if it matters where it’s called
Looking through your code, I dont see the fire event. Either you forgot it or you’re adding this ontol of what I already have. Going with the latter, I still am confused as to why you want to create UUID for all fire events and set attributes. It would make sense to just use the Attributes.changed event instead to save time, but I was trying to avoid using attributes and globals. (I was told using _g. Is a surefire way of ruining your game)
if the module is required once, you can simply make one signal. That code is for multiple scripts requiring the module. You could remove the ID/_G parts and just keep Signal = require(path).new() if it’s a one-off module.
But your post says the module script is within replicated storage. Which is just a storage space saved for both server and client. I’m trying to have it done within start player scripts. Could it be done there? Or did you pick replicated storage for a reason?
Sorry, but ya lost me in the complexity of your script. I wanted to use bindable because it sounded so easy, but if it’s this much of a hassel, it sounds like I should just use attribute changed events instead
ReplicatedStorage is just the location of the ModuleScript and the BindableEvent, the two local scripts which are communicating are in StarterGui and StarterPlayer.
My method isn’t necessary, and it looks like Forummer is gonna assist you further, I’m a bit too tired now to explain in depth on the signal implementation.
I found out the solution to my code was for me to stop being stupid. I didnt realize that in the parameters of what i passed through was less than 0 and more than what the player had. therefor, it would always return “error. value is less than”. but since i never printed this out, i never saw any errors. Module scripts do fire the event with a simple BE:fire(). im giving you the solution tho, because you showed binding the event calls into task.delay or task.wait