Maybe disable 200 local variable limit on local scripts?

Organized as in I have no problem quickly locating what I want. I remember all the variable names. I don’t see the purpose of changing this particular clientside code into modulescript stuff. Modulescripts are your preference, it doesn’t make my way of doing it somehow better.

Wow, lots going on in this thread. I agree with the points about: putting it into a table; refactoring; modulizing.

One trick I’ve learned the hard way is to make sure that my variables are only within the scope of which they are needed. If I have a local variable in my “global” scope and its only used in a single function, then that can obviously be moved into the function itself.

Another tip is to possibly implement some functional programming techniques so that functions are stateless and thus data is simply passed around and returned. That might be a pain to implement into an existing code-base of yours though.

Not sure what you mean by stateless, but yeah I went back and got rid of a few variables I thought I would need globally. I reduced it by 15 or so. So I’m about one completely new feature or two away from running into the limit again

Stateless simply means that they can exist by themselves and are entirely independent of the rest of the code. In other words, the only variables it accesses comes from either itself or its arguments. It never accesses things outside of its scope (no “upvalues” as I think Lua calls them?).

1 Like

Oh that’s the official term? Yeah that sounds like it would be great to have, very easy to move around. But what if I want it to access a variable rather than a value?

Your problem was that you had too many variables in a function, right? The purpose of modularizing would be to fix that.
Just my 2c

My problem is I have too many variables that I’d like to keep in the global scope. That’s because they are accessed more than once(or many times) in the script. If I can put them into modules, I could just put the variables inside the function and then no more problem.

In the case of functional programming in Lua, you’d just have to return the changed values and such. Can’t pass around references/pointers in Lua (except for objects/tables). C# has the nice capability of out and ref parameters.

A misconception that I see several times in this thread is that scoping using do…end somehow gets around the local variable limit. It does not. The only way to get around the local variable limit and still use local variables is to move them into functions.

And yeah, this limit is not easy for us to change since it requires extending the bytecode to 8b/insn instead of 4b, and changing the insn encoding along with serialized format.

If you don’t want to use module scripts, you could still break up the code into “objects” using tables - that would migrate a lot of functions into “methods” etc.

3 Likes

A function that does not affect anything outside of it’s own scope is called a “pure function”, functions that change global state are considered to have “side effects” and it is largely considered a bad practice though not everyone agrees. I find pure functions keep my code cleaner, easier to understand and makes paralleling (multi-threading) much safer since you don’t have to worry about multiple execution paths trying to manipulate the same global variables at the same time or wrap locks around everything.

Using a table called something like Global (or _global or _Global) and keeping your game state in that is perfectly acceptable and not unlike patterns I see in lots of applications. Pretty much every application has a global state it has to maintain, using global variables in a very large application gets messy quick and can cause all kinds of multi-threading issues, and some environments don’t even provide a global scope (and loaded libraries don’t have access to the global scope anyway), so creating a “global” data collection that can be passed around is sometimes the only reasonable answer.

It also makes unit testing much, much easier though that’s a different discussion.

2 Likes

Yes it does? The limit is on the number of active local variables.

~> % luac -p -l - <<EOF
local a = "a"
local b = "b"
EOF

main <stdin:0,0> (3 instructions at 0x600029540)
0+ params, 2 slots, 1 upvalue, 2 locals, 2 constants, 0 functions
        1       [1]     LOADK           0 -1    ; "a"
        2       [2]     LOADK           1 -2    ; "b"
        3       [2]     RETURN          0 1
~> % luac -p -l - <<EOF
do local a = "a" end
local b = "b"
EOF

main <stdin:0,0> (3 instructions at 0x600029540)
0+ params, 2 slots, 1 upvalue, 2 locals, 2 constants, 0 functions
        1       [1]     LOADK           0 -1    ; "a"
        2       [2]     LOADK           0 -2    ; "b"
        3       [2]     RETURN          0 1

Note the difference in address in the second LOADK instruction of each program - in the second program Lua has reused the register that was a for b.

> assert(loadstring(("local a = 'a' "):rep(200)))
> -- no error
> assert(loadstring(("local a = 'a' "):rep(201)))
stdin:1: [string "[...]"]:1: too many local variables (limit is 200) in main function near '='
stack traceback: [...]
> assert(loadstring(("do local a = 'a' end "):rep(201)))
> -- no error
3 Likes

Ah, right. I stand corrected. I was thinking that the comments here implied that each scope gets a limit of 200, which is what I was referring to, but putting locals into do scopes does reduce the local lifetime, allowing later locals to reuse the slots.

My current style of development on Roblox is what @Ozzypig called “disgusting”, but I think it makes things much more streamlined and easier imo.
I don’t have to focus on what variables I need, I can just write the straight up logic.

I use a “Core ModuleScript” that stores all variables and utility functions that I might need, along with a function for loading module libraries.
It uses recursion to load all variables as camelCase into the environment from the ReplicatedStorage and main UI. If I don’t want an object to be serialized, I insert a tag into the object called “RECURSEIGNORE”

At the bottom of the script, I do the following:

local coreEnv = getfenv()

local coreMeta =
{
	__index = function (t,k)
		local v = coreEnv[k]
		if v then
			t[k] = v
		end
		return v
	end
}

return function ()
	local callingEnv = getfenv(2)
	local newEnv = {}
	newEnv.script = callingEnv.script
	setmetatable(newEnv,coreMeta)
	setfenv(2,newEnv)
end
1 Like

Ok I’ll bite.

Show me well composed code that requires more than 200 local variables per stack frame for a good reason.

I’ve never seen that in my entire life.

9 Likes

@AxisAngle may have an example. He talked about hitting the limit in his table-less flag code.

Uh well… Kinda not well organized.

The problem with Shedletsky’s proposal is that he can move the goalpost wherever he wants with that weasely “for a good reason”.

1 Like

I don’t know what you call well composed code, by default you’ll probably think my code is composed poorly, so I can’t really defend myself here. I just know that my game is pretty flexible, more efficient than previous versions(which I suppose means little) and doesn’t confuse me, and that’s good enough for me. Could I use modulescripts? Maybe. Should I make that time investment? Why? Other than this hiccup, my system works great for me.

There are some crazy epic cases, where someone might write a cloth physics script auto-generates an optimized script that uses loads of upvalues. Even then, it’s possible to efficiently use 200 upvalues, then take advantage of another 200 upvalues by passing rope end points to another function.

Edit: by crazy stuff I mean scripts that look like this:

1 Like

I mean I probably have 25 variables such as Main=script.Parent, Control=Main.Control, Interface=Main.Interface, etc, but those are for convenience, and one could potentially have 100 of those GUIs inside Main, without it making the person a bad coder.

1 Like

The only one I could think of is if you are cross compiling from a different language and the lua output is really rough.

But in that case you could also just use one giant array.