How do you guys handle Cross-Script Communication?

Hey everyone,

How do you handle cross-script communication? Especially when you need to wait for data to become available… I would just like to hear your thoughts.

The way I have thought of doing it is by setting up a table which houses all of the variables that other scripts require on Script A, then yielding on Script B till the data is available.

--- Script A

local data = {}    --- houses all of the variables

bind.OnInvoke = function(key)
     return data[key]
end

--- Script B

repeat wait(1) until bind:Invoke("mapName") -- yield until data becomes available
local mapName = bind:Invoke("mapName")  -- set the variable

Now this works perfectly when you only need data from Script A to be transfered to Script B, there’s one BindableFunction and one DataTable, however when you need data from multiple scripts to transfer to other respective Scripts, it becomes a hassle.

Note: this assumes that you only ever need one “variable”, the more variables you have the messier it gets.

--- example with multiple variables
repeat wait(1) until bind:Invoke("mapName") and bind:Invoke("mapPosition") and 
bind:Invoke("mapModel") and bind:Invoke("playersTable") 

local mapName = bind:Invoke("mapName")
local mapName = bind:Invoke("mapPosition")
local mapName = bind:Invoke("mapModel")
local mapName = bind:Invoke("playersTable")

It can get very messy, so is there any other way to handle this or is there a way to make this process even more efficient?

1 Like

I have a simple modular framework set up for both the server and the client. In all of my scripts (they’re all ModuleScripts), they first require a module provider from ReplicatedStorage (MP). MP pretty much handles requiring and storing references to every ModuleScript in the game. If I want script A to get script B, I would have something like local B = MP:Get("Core/Player/B"), and from there I could get and set any values I need, no bindable event or function needed.

That is the benefit of use the framework involving all module scripts, along with one server and one local script, You can load the modules in a specific order and that way you know it will be available.

What you and @wow13524 have suggested would definitely work if it weren’t for the fact that the variables I want are not available yet and I do not wish to require whole scripts just for a few variables.

Thus a modular approach would only work in the sense that you substitute out BindableFunctions for a ModuleScript which auomatically handles the BindableFunctions for all the different scripts.

local bind = Module:Get("ScriptB") --- fetch bindableFunction
bind:Get("Variable1") --- fetch variable, and if not available yet then yield until it is.

I think you misunderstood them a little bit.

I also take the approach of 1 script that essentially acts as a bootstrapper loading all the modules. I give all the modules access to one another via an overarching table that contains them all.

local Server = {}
Server.ModuleA = require( ModuleA )
Server.ModuleB = require( ModuleB )

Server.ModuleA:Initialise( Server )
Server.ModuleB:Initialise( Server )

My initialisation function in each will store Server in a variable, as well as any initialisation tasks for that module. Then in ModuleA I might get something from ModuleB by doing local theVal = Server.ModuleB.theVal

My setup is slightly more complicated as I hate having non-function values directly in my module, but the concept is still there. My bootstrapper does all of this automatically so I can just add new modules in a folder and not have to manually declare each one in the bootstrapper itself.

Do everything that you have in separate scripts in separate modules instead, and allow them to access one another.

You aren’t requiring a module just to get some variables, the module is required and used for its actual purpose, but by setting up like this instead of in separate scripts, you now don’t have to do any additional effort beyond that to access data or functions from the different modules.

Sure this works, but I would still be yielding for data to become available even with a modular setup unless I am misunderstanding.

Using your example, the following is just one of many of my Use Cases for Cross-Server Communcation

local Server = {}
Server.ModuleA = require( ModuleA )
Server.ModuleB = require( ModuleB )

Server.ModuleA:Initialise( Server )
Server.ModuleB:Initialise( Server )

--- ModuleA

local module = {}

function module:Initialise(core)

   local mapName   
   
   game:GetService("Players").PlayerAdded:Connect(function(player)
     if not mapName then
         mapName = player:GetJoinData().TeleportData[1]
     end
   end)

end

return module

--- ModuleB

local module = {}

function module:Initialise(core)
    self.core = core

    repeat wait(1) until core.ModuleA.mapName
end

return module

If the data isn’t available you’d be yielding regardless. Whether you use a bindable or a ValueObject or a modular method. If you need the data right now and it isn’t there, you’ll either do RBXScriptSignal:Wait, Instance:WaitForChild or in the case of a module, do some sort of busy-wait loop.

This isn’t a unique issue of any solution, it’s simply the issue of needing something before it’s ready. Perhaps review your system architecture to understand why you are needing something before it’s ready, and if there is something better than waiting, such as an event-based approach.

If you go with an event-based approach,

  • For BindableEvents, you’d simply connect their Event signal.
  • For ValueObjects you’d connect ChildAdded and check the child, or Changed if it already exists.
  • For modules, create a function in ModuleB that ModuleA calls when it has the map name, core.ModuleB:CreateMap( mapName ).

Those are the only two realistic choices - wait for the data, or use event-based programming.

3 Likes