Maybe disable 200 local variable limit on local scripts?

“I haven’t needed to.” - Until now.
“Everything is clean and non-repetitive” - Excellent
“There is such a thing as a game requiring lots of code” - Yes, and they use modules to help organize that code.

I can understand not wanting to go and refactor your code at this point in order to modularize it, it’s a lot of work that will probably break things and it doesn’t add any value to the game from the player perspective. But I will encourage you to see this as a learning experience so when you’re planning your next game you consider using modules.

3 Likes

Im organized, so I just dont get why I need modules. I use them where necessary. Its just a personal preference. If I could easily do it and make it 100% seamless with little time investment, I’d do it. It definitely is a nifty feature, just too late to implement atm. Modules wouldnt get rid of the 200 limit anyway, only delay it.

You keep using the word “organized,” I do not think it means what you think it means.
Organization and modularity go hand-in-hand with Lua. If you don’t want to use ModuleScripts, this pattern can help:

local ExposedFunction; do
	local somePrivateVariable = 1
	
	function ExposedFunction()
		somePrivateVariable = 5
	end
end

do blocks might sound ugly to you, but their whole point is grouping statements into manageable chunks of logic. From there, you can structure your code to properly separate responsibilities and make things maintainable. It’s a hell of a lot more efficient than performing a table lookup whenever you need to access a variable.

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.