I am currently reading about ModuleScripts on the official documentation [here].
There it says:
A ModuleScript is a script type that returns exactly one value by a call to require(). ModuleScripts run once and only once per Luau environment and return the exact same value for subsequent calls to require().
What exactly is meant with a Luau environment? Is a Luau environment the scope of a single script? Or is it the sum of all scripts that run on the client or server?
I need to understand this because i’m having a lot of referencing errors when working with ModuleScripts in a Single-Script-Architecture.
I guess you could put it as… a Luau environment is the context of one Luau Virtual Machine.
A Luau VM interprets the bytecode which all scripts are compiled to an executes it. The Luau environment is basically the area which all your scripts run and interact with each other.
This is actually due to behaviour from Lua, the language Luau is derived from. When the C++ source code needs to run Lua code, it needs to register all the Lua scripts that need to be run to one Lua environment:
int main() {
lua_State* L = luaL_newstate(); //this is creating a new Lua environment
int loadRes = luaL_dofile(L, "myFile.lua"); //this is registering scripts to the environment
if (loadRes != LUA_OK) { //if something went wrong loading the script
}
//since this Lua script is now registered to the Lua environment,
//all it's globals, etc. can be called from C++. Additionally,
//C++ can register functions which are callable from the Lua
//scripts this environment has loaded.
//however, if we had a second Lua environment, we would
//need to register all the scripts, etc. It's like this on the VM, in a way;
//you have two separate execution threads of Luau code.
lua_close(L);
}
If you use things like Parallel Luau, you are using multiple Luau VMs - hence you have multiple Luau environments, one for each VM. So, you would need to require the module from both contexts. That’s what the docs are referring to.
“Luau environment” in the blockquote you provided refers to a script (local or server), which is both an independent translation unit and a virtual machine instance. To put it extremely simply, the “environment” is the thing that ultimately stores all your variables, functions, objects, etc.
Luau environment manages variable scope, function access, and built-in globals during script execution; a required script runs in its own separate Luau environment. In Luau, each script, including required ones, runs in its own separate environment, meaning it has its own scope for variables and functions.
The Luau environment itself consists of the Luau stack, the Luau heap, and the Luau code that has been written (as bytecode).
The stack temporarily stores data. This is things like functions, their parameters, return values. It’s accessible from C++ and Luau, primarily used for sharing data between the two.
The heap is an unordered area of memory which is used for more long-term data structures like userdatas.
The underlying C++ code of the Roblox engine maintains this Luau state. That’s what provides the sandboxing (checking calls, data, etc.), the APIs, what globals are registered and callable, etc.
Roblox instances are just userdatas which use metatables as a wrapper to call the corresponding underlying C++ API code. For example, game:GetService:
game userdata’s __namecall metamethod is triggered - this is written in C++
__namecall calls the corresponding API method - this is also C++ code
the result is returned to __namecall
__namecall returns this result to Luau
This is also why executors can hook metamethods in the way they do - they overwrite the __namecall metamethod of the userdata instance, effectively intercepting the already existing “bridge”. They can run their own custom logic depending on conditions, or they can pass the call on to the original metamethod, which then bridges the call to C++, etc.
(hence why you need to put return when calling the unhooked version of a hooked metamethod in exploit scripts). They need to do this because they aren’t registered to the same Luau environment as the game scripts, but they interact with the same VM.
TL;DR:
The C++ code and game engine itself, as well as APIs, are not a part of the Luau environment. Roblox instances are userdatas with metatables which act as wrappers to call the corresponding C++ code for the requested API.
this actually does relate to the C++ code, because as I said in my original post:
as each lua_State will cache the result for other scripts registered to the same state. I don’t mean to be rude, but your post didn’t actually mention Luau environments in the context of requiring modules.
I’m not here to debate either, I just wanted to clear that up. All done now, glad we sorted it out.
Thanks for all your answers. Unfortunately i am still a bit confused.
When i have the following structure:
Only the LocalScripts in the “StarterPlayerScripts” folder seem to share the cached result of the ModuleScript.
The LocalScript in “StarterCharacterScripts” has it’s own returned value.
The LocalScritps print “module.number” and one of the scripts changes the value after 1 second.
After 3 seconds all the LocalScripts print the number again with the following result:
As you can see the “StarterCharacterScripts” and “StarterPlayerScripts” don’t share the same cached value from the ModuleScript.
I am super confused about this behaviour and this makes me unable to code correctly in roblox because i often run into cases where i’m working with the wrong references.
There’s an exception to the trend - scripts in StarterCharacterScripts are tied to the character’s lifecycle, and when the character respawns, they are run again with a new execution context. Hence, they are given their own execution context separate to others, and because of this they don’t share the same caching state with scripts tied to PlayerScripts.
Although run under the same VM, they do have different caching states.
also - make sure you’re requiring the cloned one, not the one in the starter scripts container.
Starter[x]Scripts clone the instances under them: StarterPlayerScripts go under Player->PlayerScripts as cloned scripts and StarterCharacterScripts go under Player->Character as cloned scripts (if the character dies then the character scripts go poof). You most likely have a mix-up of what you require. Doing something along the lines of require(game:GetService("Players").LocalPlayer.PlayerScripts.ModuleScript) should work just fine.