Sharing variables between scripts?

What are your ways of sharing variables between scripts?
I’ve tried:

  • _G.
  • shared
  • Modules
  • ValueInstances

I sometimes use _G. to share variables between local scripts and local scripts and between server scripts and server scripts separately, but I’m not sure about it’s cons.

12 Likes

AFAIK _G doesn’t have any specific cons, other than not being able to share variables/functions etc. between local scripts and server scripts. To do so, you would need to use modulescripts or remote/bindable events/functions and other methods. Personally, for sharing variables and functions, I prefer to use modulescripts, since they seem more practical and easier to use (imo). It all depends on your preferences and needs.

4 Likes

_G has the con of not knowing when the variable actually exists without a lot of gross checks. It’s generally seen as a bad practice.

Values and ModuleScripts are both good depending on your case. I generally have no need to share variables between scripts, I much prefer just signaling what I actually need to get done, but stuff like attack and damage multipliers or stuff I want to distribute between the server and the client I use values.

9 Likes

All , Depending on what the script does.

2 Likes

Although initial ModuleScript values are shared between server and client, changing them in one side with a script doesn’t seem to have effect in the other (eg. if I change a ModuleScript value in a server script it’s still the same for clients). Correct?

1 Like

Correct. ModuleScripts will not automatically transfer changes between the server and the client.

Using a ModuleScript is basically strictly better than using _G.

Literally the only advantage of using _G over a ModuleScript is that it’s slightly more terse thanks to not needing the require at the start of the script. On the flipside, a ModuleScript:

  • Enforces a strict order of dependency. You know exactly what Modules depend on what other ones thanks to the requires and furthermore you know that the thing you’re depending on will actually be there when you use it—for free, you don’t need any awkward wait loops on _G variables.

  • Gives you scoping information. You have to name the Module that you put a particular global variable in. That name gives you information on what that variable is for / what code is handling it.

  • Makes you think about your code boundaries more carefully. When you can’t just touch whatever you want from anywhere in the place code at a moment’s notice and have to add the require it makes you think for a second if you actually want to add a given dependency here.

26 Likes

Wouldn’t,

local deps = require(ModuleScript_For_TableOfFunctions)
deps.foo()

and

local deps = _G.TableOfFunctions
deps.foo()

be basically the exact same thing?

The only disadvantage that I can see is that the global variable might not have loaded and that’s it.

A couple of advantages with _G is your code can run in the script that defined it. Allowing you to make robust and easily flexible one with loops and interchanging events listening to all sorts of stuff.

You can also just run a function really fast and memory efficient without going through the trouble of adding the table to memory and manually garbage collecting it.

You might say that these are really small changes and don’t really matter at all. But I’m a guy who hit the “60 upvalue limit” of Lua while heavily optimizing my Engines and APIs. This sort of stuff is what enabled me to create my own 2D GUI Physics Engine on Roblox with 729 active objects.

You probably never need to worry about efficiency of these things and memory management so I think it all boils down to preference. I use both of them. Sometimes the other is easier and faster to use than the other and I use that.

9 Likes

This is one heck of a disadvantage, FYI.

Not to mention that other code can edit your variables without you ever knowing they’ve been changed, which is bad. Your code could suddenly break because of overlapping identifiers which would be a pain to debug.

What…? You can call functions in modules as well. If you need long-running logic, wrap the loop in its own thread so that the require(…) call can return. What you’re suggesting is definitely not a good reason to throw your methods and variables in a global environment.

Lua has a 200 locals limit. There’s also do … end statement so that you can avoid this limit. There’s absolutely no situation where one piece of your code should have access to more than 200 data fields at once, or even 60+.

3 Likes

This is only the case when using multiple Scripts/LocalScripts running out of sync. With a single entry-point script, libraries/utilities can be added to _G before code that depends on them can run.

There are utility modules I use in nearly half my moduleScripts. The less I need to type, the better.

If you’re worried about conflicting with other keys in _G for portability, write a command that automatically recurses through modules and inserts something like this on the first line of every moduleScript:

_G = require(script.Parent.Parent.Parent._G);
3 Likes

Indeed they are the same except for that one disadvantage… so how exactly is the ModuleScript version not better given that it doesn’t have the disadvantage?

It’s generally a bad idea to put loops and external event handling setup in your ModuleScripts when you can avoid it. You should try to put the root of your event handling in your scripts, and have them use ModuleScripts to process those events.

I’m skeptical that that issue is at all related to this one. If you hit those limits chances are your code is not modularized as well as it should be or doesn’t have good enough division of responsibility (has functions doing too many different things at the same time). I’ve even written a very complex 6000 line ModuleScript (MagicGameState) in this place using local variables for everything without hitting the local or upvalue limits.

We’ll have to agree to disagree on that one.

2 Likes

You should never add anything mutable to _G, this is very bad:

_G.Variable = 1

_G.Variable = _G.Variable + 1

Instead, _G should be used to help organize larger projects with 100+ moduleScripts.
Instead of writing something like this all the time:

local Event = require(game:GetService("ReplicatedStorage").Modules.Utility.Event)

local event = Event.new()

You can instead use _G and type this:

local event = _G.Utility.Event.new()

With what I said in my previous post applied, there are zero disadvantages.
My MMO Shard Seekers has over 100k lines of code across 1500 ModuleScripts, and using _G for convenience is the best thing I’ve ever done.

11 Likes

You certainly don’t legitimately have 1500 Module"Scripts" though, in the sense of those ModuleScripts actually containing code with non-trivial logic, most of them are going to be ones that contain data moreso than code. That sounds like something that you could actually use a ModuleScript to handle / namespace.

ModuleScript AICode:

return {
    SomeDude = require(script.SomeDude)
    SomeOtherDude = require(script.SomeOtherDude)
    ...
}

I’m betting that your 1500 ModuleScripts could really be namespaced down to less than 100 actual legitimate namespaces worth of stuff.

1 Like

That’s not what I said, I mean cases like where you accidentally overwrite functions in _G with other functions and that causes your code to behave unexpectedly. Assuming the case where no proper scheme is used, this can happen if you haphazardly add things under common identifiers in _G.

If you stick to a proper scheme for things stored in the global environment, which you are doing, yes. That’s not what the person I was replying to is doing (I only have to assume).

2 Likes

Not really. I’ve been working on Shard Seekers full time for years, and the game is just huge at this point. Some modules definitely store data, but I have a data system that compiles that out into huge scripts before I publish. The game has more like 1000 moduleScripts once it’s compiled.

Here’s something I posted about it over a year ago:

2 Likes

I have never had this problem, but if you’re worried about that, you can set _G’s metatable and error when a script tries to make a change.

1 Like

Okay, if you have a system like that then the whole argument is moot. If you’re running things through a compiler it doesn’t really matter what you’re doing at the end of the day as long as the compiler verifies consistency / generates a valid output. Using _G as a backend for something is completely fine.

My points still stand for any remotely “normal” Roblox coding.

4 Likes

Fair enough :ok_hand:

2 Likes

Someone linked me here, but a good way to do this is to use Remote Events. Probably the best way.

2 Likes