Hello! the title says it, my bindableevent doesn’t fire, which makes my script yield indefinitely, and it is very frustrating! I’ve been searching for a solution for days!!
Can someone please help me?
My modulescript that yields:
local module = {
__loaded = false,
_replicas = {},
}
local LoadedSignal = script.LoadedSignal
function module:GetReplicas()
if (not self.__loaded) then
print("Waiting for the replicas...") -- This prints
LoadedSignal.Event:Wait()
print("Waited!") -- This does not print
end
return module._replicas
end
return module
The modulescript’s init script:
local ReplicaController = require(game:GetService("ReplicatedStorage").Lib.ReplicaController)
local replicasModule = require(script.Parent)
local loadedSignal = script.Parent.LoadedSignal
-- Init
ReplicaController.RequestData()
-- Replicas
ReplicaController.ReplicaOfClassCreated("Time", function(replica)
print("Replica received!")
replicasModule._replicas[replica.Class] = replica
end)
replicasModule.__loaded = true
loadedSignal:Fire() -- The BindableEvent fires here
print("Loaded!", replicasModule) -- This prints
From the code I can see, this seems like a pretty weird issue. I would suggest creating another event connection within that module to see if that connection fires. You could also replace the event wait with a repeat until _loaded is true. I understand this probably isn’t ideal, but it’s the best solution I could come up with the information I have.
Are you certain :GetReplicas() is being called before the init script runs?
If the function gets called after the init script runs, then the bindable is being fired before the function can even attach its :Wait() to it, causing the indefinite wait.
I was thinking this too, but he does say “Waiting for the replicas…” prints so I figured it was another issue. Although it may of just printed after the event fired and they could have overlooked that.
Yes, as the scripts prints “Waiting…”, and if :GetReplicas() was called after the init script ran, it just wouldn’t wait and directly return the replicas.
I tried replacing the BindableEvent.Event:Wait() by a repeat, but now I think that the ModuleScript and the Init script might be running under different identities as the ModuleScript’s values are not the same for both scripts. EDIT: I checked with printidentity(), and they run under the same identity.
New code that repeats:
local module = {
__loaded = false,
_replicas = {},
}
local LoadedSignal = script.LoadedSignal
function module:GetReplicas()
repeat
task.wait(1)
print(module)
until module.__loaded == true
--if (not self.__loaded) then
-- print("Waiting for the replicas...")
-- LoadedSignal.Event:Wait()
-- print("Waited!")
--end
return module._replicas
end
return module
Output:
08:37:34.649 ▼ {
["GetReplicas"] = "function",
["__loaded"] = true, --[[ Here it says that it is loaded ]]--
["_replicas"] = {} --[[ But there are no replicas in the modulescript ]]--
} - Client - Init:18
08:37:35.244 Replica received! - Client - Init:11
08:37:35.244 [ReplicaController]: Initial data received - Client - ReplicaController:974
08:37:35.662 ▼ {
["GetReplicas"] = "function",
["__loaded"] = false, --[[ And here it says that it isn't loaded ]]--
["_replicas"] = {} --[[ It's still empty ]]--
} - Client - Replicas:12
This is a really interesting issue that I haven’t come across before. I tried testing this myself with a simpler version of your scripts, and it works perfectly fine:
I tried doing the repeat method but in the localscript it says that __loaded is true, but in the modulescript it says that it is false .
I call :GetReplicas only in these LocalScripts: (search for the highlighted --TODO in the start of each script)
Time Gui
-- Modules
local gameModule = require(game:GetService("ReplicatedStorage").Lib.GameModule)
local Icon = require(game:GetService("ReplicatedStorage").Lib.Icon)
local Replicas = require(game:GetService("StarterPlayer").StarterPlayerScripts.Replicas):GetReplicas() --TODO-- here!!
-- Functions
local function secondsFormat(seconds): (string)
return string.format("%02i:%02i", seconds / 60 % 60, seconds % 60)
end
-- Variables
local currentTime
local formattedTime
local currentType
-- Topbar icon
local icon = Icon.new()
:align("Center")
:setLabel(secondsFormat(0))
:lock()
:deselect()
-- Code
currentTime = Replicas.Time.Data.Time
currentType = Replicas.Time.Data.Type
icon:setLabel(secondsFormat(currentTime).."\n"..gameModule.TIME_TYPES[currentType])
Replicas.Time:ListenToChange({"Time"}, function(newTime)
currentTime = newTime
icon:setLabel(secondsFormat(currentTime).."\n"..gameModule.TIME_TYPES[currentType])
end)
Replicas.Time:ListenToChange({"Type"}, function(newType)
currentType = newType
icon:setLabel(secondsFormat(currentTime).."\n"..gameModule.TIME_TYPES[currentType])
if newType == 4 then -- Disables the reset button when build checking so that the animations doesn't break
game:GetService("StarterGui"):SetCore("ResetButtonCallback", false)
elseif newType == 1 then
game:GetService("StarterGui"):SetCore("ResetButtonCallback", true)
end
end)
Background music script
local TweenService = game:GetService("TweenService")
local tweenInfo = TweenInfo.new(1.5)
local musicPlaylists = game.ReplicatedStorage.Audio.Music
local currentTrack: Sound = nil
local currentType = nil
local gameModule = require(game:GetService("ReplicatedStorage").Lib.GameModule)
local Replicas = require(game:GetService("StarterPlayer").StarterPlayerScripts.Replicas):GetReplicas() --TODO-- hello im here
-- code
currentType = Replicas.Time.Data.Type
Replicas.Time:ListenToChange({"Type"}, function(newType)
currentType = newType
pcall(function()
local tween = TweenService:Create(currentTrack, tweenInfo, {Volume = 0})
tween:Play()
tween.Completed:Connect(function(playbackState: Enum.PlaybackState)
currentTrack:Stop()
end)
end)
end)
while task.wait() do -- NOTE: This does NOT run every tick, as it waits for the track to end
currentTrack = musicPlaylists[gameModule[currentType]]
:GetChildren()[math.random(1, #musicPlaylists[gameModule[currentType]]:GetChildren())]
currentTrack.Volume = 0
currentTrack:Play();
local tween = TweenService:Create(currentTrack, tweenInfo, {Volume = 0.5})
tween:Play()
currentTrack:GetPropertyChangedSignal("Playing"):Wait() -- Waits for the track to end
end
Building script
-- Services
local TweenService = game:GetService("TweenService")
-- Modules
local gameModule = require(game:GetService("ReplicatedStorage").Lib.GameModule)
local Replicas = require(game:GetService("StarterPlayer").StarterPlayerScripts.Replicas):GetReplicas() --TODO-- hereeee
-- References
local UserInputService = game:GetService("UserInputService")
local LocalPlayer = game:GetService("Players").LocalPlayer
local CurrentCamera = game.Workspace.CurrentCamera
local selectedBlock: ObjectValue = script.Parent.selectedBlock
local BuildGui = script.Parent.Parent
local Blocks = game:GetService("ReplicatedStorage").Blocks
local LocalBlocks = Instance.new("Folder")
local ConfirmUis = LocalPlayer.PlayerGui:WaitForChild("Temp").ConfirmUis
local TempUis = LocalPlayer.PlayerGui:WaitForChild("Temp")
local ConfirmUi = game:GetService("ReplicatedStorage"):WaitForChild("Gui").ConfirmUi
local Zones = game.Workspace.Platforms.Zones
LocalBlocks.Name = "TempLocalBlocks"
LocalBlocks.Parent = game.Workspace
-- Remotes & Bindables
local BuildBlockRemote = game:GetService("ReplicatedStorage").Remotes.Building.BuildBlock
-- Variables
local deleteMode = script.Parent.deleteMode
local currentType
local connections = {
onConfirmation = {},
onClick = nil, -- declared at runtime
onBlockSelected = nil
}
-- CONSTANTS
local LOCAL_BLOCK_TRANSPARENCY = 0.45
-- functions
local function sendBlock(Block: Instance, mousePosition)
BuildBlockRemote:FireServer(Block, CurrentCamera.CFrame, CurrentCamera:ViewportPointToRay(mousePosition.X, mousePosition.Y))
end
local function onBlockSelect()
if connections.onClick then
connections.onClick:Disconnect()
end
local Block: Instance? = selectedBlock.Value
--TODO// fix swipe gestures detecting as taps \\TODO--
connections.onClick = UserInputService.InputBegan:Connect(function(input: InputObject, gameProcessed: boolean)
if Block and deleteMode.Value == false
and input.UserInputType == Enum.UserInputType.MouseButton1
and gameProcessed == false then
local mousePosition = UserInputService:GetMouseLocation()
local mouseRay = CurrentCamera:ViewportPointToRay(mousePosition.X, mousePosition.Y)
sendBlock(Block, mousePosition)
elseif Block and deleteMode.Value == false
--TODO !!!!!!!!!!TODO THIS IS MOBILE ONLYYYYY RRAAAAAAAAA TODO!!!!!!!!!!!! TODO
and input.UserInputType == Enum.UserInputType.Touch
and gameProcessed == false
and #LocalBlocks:GetChildren() == 0 then
local mousePosition = UserInputService:GetMouseLocation()
local mouseRay = CurrentCamera:ViewportPointToRay(mousePosition.X, mousePosition.Y)
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.FilterDescendantsInstances = {LocalPlayer.Character}
local mouseRaycast = game.Workspace:Raycast(mouseRay.Origin, mouseRay.Direction * gameModule.REACH, raycastParams)
local PlayerZone
for count, CurrentZone in Zones:GetChildren() do
if CurrentZone:GetAttribute("userId") == LocalPlayer.UserId then
PlayerZone = CurrentZone
end
end
if mouseRaycast and gameModule.isPointInPart(
gameModule.snapToGrid(mouseRaycast.Position, mouseRaycast.Normal),
PlayerZone)
then
local NewConfirmUi = ConfirmUi:Clone()
local LocalBlock = Block:Clone()
NewConfirmUi.Adornee = LocalBlock
NewConfirmUi.Parent = LocalPlayer.PlayerGui
LocalBlock.CanTouch = false
LocalBlock.CanQuery = false
LocalBlock.CanCollide = false
LocalBlock.Position = gameModule.snapToGrid(mouseRaycast.Position, mouseRaycast.Normal)
LocalBlock.Transparency = 0.45
for _, Texture in LocalBlock:GetChildren() do -- makes all of the textures of the block transparent
if Texture.ClassName == "Texture" then
Texture.Transparency = LOCAL_BLOCK_TRANSPARENCY
end
end
LocalBlock.Size = Vector3.new(0, 0, 0)
LocalBlock.Parent = LocalBlocks
local AppearTween = TweenService:Create(
LocalBlock,
TweenInfo.new(
0.5,
Enum.EasingStyle.Exponential,
Enum.EasingDirection.Out),
{
Size = Vector3.new(2.75, 2.75, 2.75),
})
AppearTween:Play()
AppearTween.Completed:Connect(function()
LocalBlock.Orientation = Vector3.new(0, 0, 0)
LocalBlock.Size = Vector3.new(2.75, 2.75, 2.75)
end)
NewConfirmUi.Frame.Yes.Activated:Once(function()
sendBlock(Block, mousePosition)
LocalBlock:Destroy()
NewConfirmUi:Destroy()
end)
NewConfirmUi.Frame.No.Activated:Once(function()
LocalBlock:Destroy()
NewConfirmUi:Destroy()
end)
NewConfirmUi.Enabled = true
end
end
end)
end
local function activateBuilding()
BuildGui.Enabled = true
connections.onBlockSelected = selectedBlock:GetPropertyChangedSignal("Value"):Connect(onBlockSelect)
end
local function deactivateBuilding()
BuildGui.Enabled = false
if connections.onBlockSelected then
connections.onBlockSelected:Disconnect()
end
for _, Block in LocalBlocks:GetChildren() do
Block:Destroy()
end
for _, Gui: UIBase in ConfirmUis:GetChildren() do
Gui:Destroy()
end
end
-- Replicas
currentType = Replicas.Time.Data.Type
Replicas.Time:ListenToChange({"Type"}, function(newType)
-- Code
currentType = newType
if currentType == 3 then
activateBuilding()
elseif currentType == 4 then
deactivateBuilding()
end
end)
Ah I found it! You are right about the module belonging to two different identities. This is because you are requiring the module through StarterPlayerScripts which is where the server copies the scripts from into the player. The actual location of this script is in game.Players.LocalPlayer.PlayerScripts (at least the one you are setting the .__loaded in). The check for .__loaded should be correct if you call the function from this module script instead of the one in StarterPlayerScripts.
It seems that your BindableEvent is fired before all of GetReplicas function calls are reached.
That won’t work because it will only change the __loaded of the replicasModule in the Init script. A module script is not global, every time you require it, you run it again on the local/server script that you required it from, and get its returned value.
That’s why when you do .__loaded = true in one script, it won’t change it globally in all scripts that require the module.
I’d recommend replacing the BindableEvent with a BoolValue (or an attribute) and do the following:
When the replicas are loaded, set its value to true, and then in the GetReplicas function only wait if it isn’t set to true yet.
Here’s how it would look in code if you replace the BindableEvent with a BoolValue called “LoadedBoolValue”, or a Boolean attribute.
ModuleScript:
local module = {
_replicas = {},
}
local Loaded = script.LoadedBoolValue
function module:GetReplicas()
if (not self.__loaded) then
print("Waiting for the replicas...") -- This prints
if(not Loaded.Value) then --If it's false (not loaded)
Loaded:Get property changed signal("Value"):Wait() --Wait for the value to change
end
print("Waited!") -- This should print when the Loaded value is true
end
return module._replicas
end
return module
Init local script:
local ReplicaController = require(game:GetService("ReplicatedStorage").Lib.ReplicaController)
local replicasModule = require(script.Parent)
local loadedValue = script.Parent.LoadedBoolValue
-- Init
ReplicaController.RequestData()
-- Replicas
ReplicaController.ReplicaOfClassCreated("Time", function(replica)
print("Replica received!")
replicasModule._replicas[replica.Class] = replica
end)
loadedValue.Value = true -- Update the global BoolValue to let the other scripts know they can stop waiting/don't have to wait.
print("Loaded!", replicasModule) -- This prints
This is called polling, and it’s generally not a good approach.